Как оптимально записывать данные в независимый регистр сведений в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке и оптимизации систем на платформе 1С:Предприятие одной из частых задач является массовое добавление или обновление записей в независимых регистрах сведений. Классический пример — регистрация штрихкодов номенклатуры, цен товаров, аналогов или ключевых спецификаций, поступающих из внешних источников или документов. Основная сложность заключается в том, что часть записываемых данных уже может существовать в базе данных, а часть является абсолютно новой.

В этой статье мы подробно разберем, почему стандартные подходы могут приводить к критическому падению производительности, проанализируем классический паттерн оптимизации через запросы и транзакции, а также рассмотрим современный революционный инструмент — встроенный режим слияния, появившийся в платформе 1С начиная с версии 8.3.25.

В чем заключается проблема стандартных методов записи?

Рассмотрим классическую ситуацию: к нам поступает документ или внешний пакет данных (например, JSON-файл или таблица значений), содержащий список номенклатуры и штрихкодов. Нам необходимо отразить эти данные в независимом регистре сведений ШтрихкодыНоменклатуры с измерениями Номенклатура и Штрихкод.

У начинающих разработчиков часто возникает соблазн использовать один из двух очевидных, но неоптимальных путей:

  1. Использование Менеджера Записи в цикле. Для каждой строки входящего документа создается объект РегистрСведенийМенеджерЗаписи, заполняются ключевые поля, вызывается метод Прочитать(), а затем метод Записать().

    В чем проблема: Этот подход порождает так называемую проблему "N+1 запроса". Если в документе 100 строк, платформа выполнит 100 отдельных обращений к СУБД для чтения данных и еще 100 обращений для записи. На больших объемах данных это приводит к катастрофическим задержкам из-за накладных расходов на сетевой трафик и транзакционную логику СУБД.

  2. Перезапись регистра через Набор Записей. Создается объект РегистрСведенийНаборЗаписей, заполняется новыми данными и записывается с параметром Замещать = Истина.

    В чем проблема: Набор записей в независимом регистре требует жесткой установки отборов по всем измерениям, для которых мы хотим перезаписать данные. Однако мы не можем установить отбор вида "В Списке" для измерений набора записей. Если мы не установим отбор вовсе, метод Записать(Истина) просто очистит весь регистр сведений и запишет туда только наши 20-100 новых строк, уничтожив всю остальную базу данных штрихкодов.

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

Решение 1. Фильтрация данных через запрос и запись без замещения (для платформ до версии 8.3.24)

Если ваша информационная база работает на версии платформы 8.3.24 или ниже, то наилучшим решением будет предварительное разделение входящего потока данных непосредственно на стороне СУБД. Нам нужно отсечь те записи, которые уже присутствуют в регистре, оставив только уникальные новые строки. После этого мы сможем записать их одной транзакцией без риска получить исключение о дублировании ключевых записей.

Разберем этот алгоритм по шагам:

  1. Выгружаем входящие данные (например, табличную часть документа) во временную таблицу в запросе.
  2. Выполняем левое соединение этой временной таблицы с физической таблицей регистра сведений по всем ключевым полям (измерениям).
  3. Фильтруем результат соединения по условию ГДЕ Регистр.Измерение ЕСТЬ NULL. Таким образом, мы получаем только те строки, которых физически еще нет в нашей базе данных.
  4. Выгружаем результат запроса в таблицу значений и загружаем её в набор записей без отборов.
  5. Записываем набор в режиме добавления, передавая в метод Записать() параметр Ложь (не замещать).

Рассмотрим практический пример реализации этого метода на встроенном языке 1С:


// Предположим, что ИсточникДанных - это ТаблицаЗначений с колонками Номенклатура и Штрихкод
Процедура ЗаписатьНовыеШтрихкодыОптимально(ИсточникДанных)
    
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
    
    // Помещаем входящие данные во временную таблицу для быстрой обработки на сервере СУБД
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Исходник.Номенклатура КАК Номенклатура,
        |   Исходник.Штрихкод КАК Штрихкод
        |ПОМЕСТИТЬ ВТ_ВходящиеДанные
        |ИЗ
        |   &ТаблицаИсточника КАК Исходник";
        
    Запрос.УстановитьПараметр("ТаблицаИсточника", ИсточникДанных);
    Запрос.Выполнить();
    
    // Выбираем только те записи, которых еще нет в регистре сведений
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Входящие.Номенклатура КАК Номенклатура,
        |   Входящие.Штрихкод КАК Штрихкод
        |ИЗ
        |   ВТ_ВходящиеДанные КАК Входящие
        |       ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ШтрихкодыНоменклатуры КАК РегШтрихкоды
        |       ПО Входящие.Номенклатура = РегШтрихкоды.Номенклатура
        |           И Входящие.Штрихкод = РегШтрихкоды.Штрихкод
        |ГДЕ
        |   РегШтрихкоды.Номенклатура ЕСТЬ NULL";
        
    РезультатЗапроса = Запрос.Выполнить();
    
    Если Не РезультатЗапроса.Пустой() Тогда
        
        ТаблицаДляЗаписи = РезультатЗапроса.Выгрузить();
        
        // Создаем набор записей без установки отборов
        НаборЗаписей = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьНаборЗаписей();
        НаборЗаписей.Загрузить(ТаблицаДляЗаписи);
        
        // Записываем данные в режиме добавления (Замещать = Ложь)
        // Это предотвратит очистку существующих данных в регистре
        НаборЗаписей.Записать(Ложь);
        
    КонецЕсли;

КонецПроцедуры

Этот метод работает чрезвычайно быстро, так как сравнение сотен тысяч записей перекладывается на плечи СУБД, которая использует индексы таблицы регистра. В базу данных уходит всего один запрос на чтение и один пакетный запрос на запись.

Внимание: Риски блокировок СУБД при записи наборов без отбора

Несмотря на высокую скорость работы первого решения, при его использовании в высоконагруженных многопользовательских системах (HighLoad) кроется серьезная опасность. Когда мы вызываем метод НаборЗаписей.Записать(Ложь) без установленных отборов, СУБД не знает заранее, какие именно диапазоны записей будут изменены.

В зависимости от настроек СУБД (MS SQL Server, PostgreSQL) и объема записываемых данных, это может привести к эскалации блокировок — вместо блокировки конкретных строк (Row Locks) СУБД может заблокировать целые страницы данных (Page Locks) или даже всю таблицу регистра сведений (Table Lock). В этот момент другие пользователи, пытающиеся записать или прочитать штрихкод совершенно другого товара, столкнутся с ожиданиями на блокировках или ошибкой Deadlock (взаимная блокировка).

Как снизить риски блокировок на старых платформах:

Решение 2. Современный подход с использованием встроенного "Слияния" (Платформа 8.3.25 и выше)

Разработчики технологической платформы 1С детально проанализировали эту проблему и в версии **8.3.25** представили кардинальное обновление механизмов работы с независимыми регистрами. Теперь нам больше не нужно писать сложные запросы с левыми соединениями и заботиться об исключениях уникальности записей.

В метод Записать() объекта РегистрСведенийНаборЗаписей был добавлен новый параметр, принимающий значения системного перечисления РежимЗамещения (или ReplacementMode в англоязычном варианте):

Проанализируем пример кода с использованием нового режима слияния:


Процедура ЗаписатьШтрихкодыСлиянием(ИсточникДанных)
    
    // Создаем набор записей без установки отборов
    НаборЗаписей = РегистрыСведений.ШтрихкодыНоменклатуры.СоздатьНаборЗаписей();
    
    // Напрямую загружаем всю таблицу входящих данных в набор записей
    НаборЗаписей.Загрузить(ИсточникДанных);
    
    // Выполняем запись в режиме слияния. 
    // Платформа сама определит, какие строки добавить, а какие обновить.
    // При этом будут наложены минимальные и максимально точечные блокировки на уровне СУБД!
    НаборЗаписей.Записать(РежимЗамещения.Слияние);

КонецПроцедуры

Использование режима РежимЗамещения.Слияние дает разработчику сразу несколько весомых преимуществ:

  1. Минимум кода: Исчезает необходимость в конструировании сложных запросов с временными таблицами.
  2. Беспрецедентная скорость: Все операции выполняются непосредственно СУБД в рамках единого пакета инструкций на физическом уровне баз данных.
  3. Безопасность блокировок: Платформа оптимизирует предикаты блокировок под конкретную СУБД, сводя к минимуму вероятность возникновения конфликтов блокировок у параллельно работающих пользователей.

Подведем итоги

Выбор оптимального метода записи данных в независимый регистр сведений напрямую зависит от версии технологической платформы, на которой развернуто ваше прикладное решение:

← На главную