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

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

При оптимизации регламентных расчетов и массовых загрузок разработчики часто сталкиваются с дилеммой: использовать построчную запись через РегистрСведенийМенеджерЗаписи или выполнять пакетное замещение через РегистрСведенийНаборЗаписей. Построчная запись на объемах в десятки тысяч строк работает медленно, а наивная пакетная схема «прочитал период — очистил/заместил — записал» открывает критическое окно конкурентного доступа (lost update), при котором параллельно внесенные пользователями или другими сеансами строки затираются без ошибок.

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

Причина возникновения Lost Update при пакетной записи

Проанализируем механику работы метода НаборЗаписей.Записать(Истина). На уровне СУБД (MS SQL Server, PostgreSQL) этот вызов всегда трансформируется в операцию полной очистки по отбору (DELETE FROM ... WHERE ...) с последующей вставкой новых строк через серию команд INSERT.

Если между операцией чтения данных набором (или формированием таблицы значений в памяти) и фактическим вызовом Записать(Истина) другой сеанс успевает добавить или изменить запись с тем же периодом, платформа при выполнении транзакции набора удалит чужую запись. Операция чтения НаборЗаписей.Прочитать() не накладывает долгосрочных блокировок на чтение в управляемом режиме блокировок, поэтому окно между чтением и записью остается открытым.

Решение 1: Явная управляемая блокировка в единой транзакции

Для предотвращения неявной перезаписи чужих данных необходимо гарантировать, что диапазон измерений и период защищены от параллельных модификаций с самого начала расчета до фиксации транзакции. Снять управляемую блокировку до окончания транзакции невозможно — она удерживается до вызова ЗафиксироватьТранзакцию() или ОтменитьТранзакцию().

Разберем по шагам правильный шаблон установки управляемой блокировки:

  1. Открываем явную транзакцию с помощью НачатьТранзакцию().
  2. Создаем объект БлокировкаДанных и добавляем элемент блокировки для целевого регистра сведений в режиме РежимБлокировкиДанных.Исключительный.
  3. Устанавливаем значения отбора по периоду и ключевым измерениям. Важное правило: область управляемой блокировки не должна быть уже области отбора набора записей, иначе незаблокированные строки будут затерты при записи.
  4. Вызываем Заблокировать().
  5. Выполняем расчет, считываем или заполняем РегистрСведенийНаборЗаписей с аналогичным отбором и вызываем Записать(Истина).
  6. Фиксируем транзакцию вызовом ЗафиксироватьТранзакцию().

Посмотрим на пример кода с защитой периода и разреза:


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

    Набор = РегистрыСведений.ПоказателиНоменклатуры.СоздатьНаборЗаписей();
    Набор.Отбор.Период.Установить(РасчетнаяДата);
    Набор.Отбор.Склад.Установить(ТекущийСклад);

    // Заполнение набора рассчитанными данными
    Для Каждого СтрДанных Из ТаблицаРасчета Цикл
        Запись = Набор.Добавить();
        ЗаполнитьЗначенияСвойств(Запись, СтрДанных);
        Запись.Период = РасчетнаяДата;
        Запись.Склад = ТекущийСклад;
    КонецЦикла;

    Набор.Записать(Истина);

    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

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

Решение 2: Использование системного свойства «РежимЗамещения»

Начиная с современных версий платформы 1С:Предприятие 8.3, метод Записать() набора записей регистра сведений поддерживает параметр РежимЗамещения, исключающий деструктивное удаление данных по всему отбору:

  1. РежимЗамещения.Слияние — выполняет точечную вставку новых строк и обновление существующих по совпадению ключевых полей. Записи с другими измерениями внутри того же периода не удаляются, что исключает потерю строк без необходимости блокировать весь период.
  2. РежимЗамещения.Обновление — обновляет значения ресурсов только у тех записей, чей ключ уже есть в регистре, не создавая новые строки.
  3. РежимЗамещения.Удаление — выполняет точечное удаление только тех строк, которые переданы в наборе.

Посмотрим на пример использования точечного слияния данных:


Набор = РегистрыСведений.ПоказателиНоменклатуры.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(РасчетнаяДата);

// Заполняем набор только изменившимися данными
Для Каждого Стр Из ТаблицаНовыхПоказателей Цикл
    Запись = Набор.Добавить();
    ЗаполнитьЗначенияСвойств(Запись, Стр);
    Запись.Период = РасчетнаяДата;
КонецЦикла;

// Запись без очистки чужих измерений за дату
Набор.Записать(РежимЗамещения.Слияние);

Решение 3: Распараллеливание пакетной записи и разделение по диапазонам

При больших объемах (от десятков тысяч записей) удержание единой транзакции на весь объем данных приводит к росту размера журнала транзакций СУБД (LDF/WAL) и риску эскалации блокировок СУБД до уровня таблиц. В то же время запуск отдельного фонового задания на каждый единичный ключ порождает избыточные накладные расходы на создание сеансов.

Оптимальная стратегия параллелизации:

  1. Разбиение на фиксированное число потоков (N потоков): делим массив номенклатуры на группы по остатку от деления числового кода или хеша ссылки (ХешФункция(Ссылка) % N).
  2. Изоляция блокировок по потоку: каждый фоновый поток накладывает исключительную блокировку только на свой диапазон данных и период, обрабатывая порцию в 1 000 – 5 000 строк в рамках одной транзакции.
  3. Защита от повторного запуска регламента: для предотвращения наложения параллельных регламентных заданий друг на друга используется не строковый флаг в константе (который может зависнуть при аварийном завершении), а попытка наложения управляемой блокировки на специальный служебный регистр в начале работы задания. Если замок занят другим активным заданием — сеанс корректно завершает работу.
← На главную