Линия консультации: 8 (495) 204-10-28
Разработка и внедрение бизнес-систем

История о том, как бахнуть в базу данных 150 млн записей за раз и не заставить нервничать сисадмина


29.10.2021


Вообще, интеграцию с сервисом ГУВМ МВД по загрузке списка недействительных паспортов мы сделали давно. Примерно также давно, как выпустили первый релиз «АДС: Комплексная проверка контрагентов». Но наш вариант реализации не был лишен некоторых недостатков. В частности, периодически ловили такие проблемы как:

  • Иногда, паспорта из этой базы нет-нет да пропадают, но реквизиты пропавших паспортов в БД оседали и, при проверке, все-равно, выбивало, что паспорт недействителен. Представляете себе чувства этого человека? Он, значит, собрал стопку документов, прошелся по всем властным структурам, доказал действительность своего паспорта, а ему при простой операции опять про недействительность говорят…
  • Процесс обновления базы был весьма непростым делом. Дело в том, что из-за своего размера база могла обновляться очень (прямо очень) долго, особенно, если у вас нет компьютера с терабайтом оперативки. А обновлять ее надо часто, оптимально – раз в день. И очень хочется, чтобы этот процесс можно было закрыть ночным регламентом.
  • Из-за очень большого объема, стандартные инструменты 1С отрабатывали некорректно, что вызывало полную остановку работы сервиса из-за блокировки в момент записи данных в регистр сведений, как средствами 1С, так и средствами самой СУБД MSSQL. Согласитесь, тоже не фонтан.

Посидели, подумали и решили, что нужно с этим что-то делать. Мы пошли путем оптимизации работы загрузчика, в части использования команд на уровне СУБД. Загрузку данных в СУБД выполнить командой BULK INSERT с полной подменой таблицы регистра сведений и с записью в точную копию таблицы на уровне СУБД, с последующим переименованием таблиц. Тем самым выполняем подмену данных без остановки процесса и блокировок. Для решения этой задачи мы бросили умы наших лучших оптимизаторов. И не прогадали!

На выходе мы получили сервис способный обработать большой массив записей, а база недействительных паспортов содержит более 150 млн записей. Сейчас сервис работает без необходимости остановки работы системы, без бесконтрольных блокировок, без хвостов ранее удаленных записей. А также обновление можно запускать каждую ночь с полной уверенностью, что к утру все прогрузится и работа продолжится. Ну и да, сисадмин нашего клиента нам спасибо сказал :)

Теперь давайте немного расскажем про то, как именно, технически, был достигнут необходимый результат. Сразу скажу, информация далее по тексту техническая и требует технических знаний для ее осознания.

Для корректного копирования структуры таблиц на уровне СУБД необходимо определить структуру хранения:

  
	 МассивИменМетаданных = Новый Массив();
	 МассивИменМетаданных.Добавить("РегистрСведений.адс_кпк_Паспорта");
	 тзСтруктура = ПолучитьСтруктуруХраненияБазыДанных(МассивИменМетаданных,Истина);
	 ИмяТаблицы1С = СтрЗаменить(тзСтруктура[0].ИмяТаблицы,".","_");
	 ИмяТаблицыБД = тзСтруктура[0].ИмяТаблицыХранения;
  

Формируем текст запроса к СУБД, для загрузки данных из файла CSV используем временную таблицу, так же в рамках данного запроса выполняем проверку на наличие копии таблицы данных и создаем точную копию исходной таблицы хранения регистра сведений на уровне СУБД:

  
	 ТекстЗапросаСоздатьВременную = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("
	             |IF object_id('dbo.%1_tmp', 'U') IS NOT NULL DROP TABLE [dbo].[%1_tmp];
	             |SELECT * INTO [dbo].[%1_tmp] FROM [dbo].[%1] WHERE 1 = 0;
	             |IF object_id('dbo.TempPasport', 'U') IS NULL
	             |CREATE TABLE [dbo].[TempPasport]
	             |(
	             |    Seria    NVARCHAR(4) NOT NULL,
	             |    Number     NVARCHAR(6) NOT NULL
	             |) ELSE TRUNCATE TABLE TempPasport;",
	             ИмяТаблицыБД));
  

Формируем текст запроса загрузки данных из файла во временную таблицу командой BULK INSERT:

  
	 ТекстЗапросаЗагрузка = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("
	             |IF object_id('dbo.TempPasport', 'U') IS NOT NULL
	             |BULK INSERT [dbo].[TempPasport]
	             |FROM '%2' 
	             |WITH
	             |(
	             |FIRSTROW = 2,
	             |BATCHSIZE = 100000,
	             |FIELDTERMINATOR = ',',
	             |ROWTERMINATOR = '\n',
	             |ROWS_PER_BATCH = 1700000000,
	             |CODEPAGE = '1251',
	             |DATAFILETYPE = 'char'
	             |);", ИмяТаблицыБД, ИмяФайла);
  

Формируем текст запроса на копирование данных из временной таблицы в подменную таблицу регистра сведений:

  
	 ТекстЗапросаВставка = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("
	             |INSERT INTO [dbo].[%1_tmp] SELECT Seria,Number,0 FROM [dbo].[TempPasport];", ИмяТаблицыБД);
  

Финальный запрос — переименование подменной таблицы:

  
	 ТекстЗапросаСкопироватьИзВременной = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("
	             |BEGIN TRANSACTION;
	             |IF object_id('dbo.%1_old', 'U') IS NOT NULL DROP TABLE [dbo].[%1_old];
	             |EXEC sp_rename '%1', '%1_old';
	             |EXEC sp_rename '%1_tmp', '%1';
	             |COMMIT;", ИмяТаблицыБД);
  

Очистим ненужные данные:

  
	 ТекстЗапросаУдалитьВременную = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку("
	             | DROP TABLE [dbo].[%1_old];
	             | DROP TABLE [dbo].[TempPasport];", ИмяТаблицыБД);
  

Собственно, а на этом все. Нехитрый набор действий позволил нам достичь необходимого результата, не ставя лишних костылей.



Хотите узнать больше о нашем продукте? Переходите по ссылке, там еще много всего интересного!


Теги:  КПК

Возврат к списку