Как избежать конфликтов блокировок при частой записи в регистр сведений без прямого SQL-инсерта?

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

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

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

Почему возникают конфликты блокировок при записи набора

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


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

Выясним причину проблем с этим кодом:

  1. Чтение всех данных регистра: Метод Набор.Прочитать() вызывается без установки отбора (Отбор.<ИмяИзмерения>.Установить(...)). Это означает, что платформа считывает из базы абсолютно все существующие строки регистра (десятки и сотни тысяч записей).
  2. Полная перезапись таблицы: При выполнении метода Набор.Записать() по умолчанию параметр Замещать равен Истина. Платформа открывает неявную транзакцию, удаляет из базы все записи, соответствующие текущему отбору (а так как отбора нет — пытается очистить всю таблицу целиком), и записывает накопленный набор заново.
  3. Эскалация блокировок (Lock Escalation): При модификации тысяч строк MS SQL Server автоматически выполняет замену гранулярных блокировок строк и диапазонов ключей на исключительную монопольную блокировку всей физической таблицы. В этот момент любая параллельная попытка чтения или записи от других 100 пользователей встает в очередь и падает по таймауту.
  4. Автоматический режим блокировок и хинты запросов: Если конфигурация работает в режиме «Автоматический», на уровне СУБД применяется жесткий уровень изоляции SERIALIZABLE. А использование конструкций вроде ДЛЯ ИЗМЕНЕНИЯ накладывает блокировки диапазонов (Key-Range), парализуя соседние транзакции даже при работе с логически несвязанными данными.

Опасности прямой вставки данных через ADODB в СУБД

Посмотрим, к каким последствиям приводит выполнение прямого INSERT INTO в обход платформы 1С:

  1. Рассинхронизация кэша: Сервер приложений 1С кэширует данные таблиц. Прямая модификация в СУБД не сбрасывает кэш платформы, из-за чего пользователи будут видеть устаревшие данные, а последующие штатные операции платформы могут привести к разрушению целостности.
  2. Нестабильность имен таблиц и полей: Имена внутренних таблиц (вида _InfoRg123) динамически генерируются платформой. При обновлении конфигурации, реструктуризации базы, выгрузке/загрузке файла .dt номера таблиц могут измениться, что приведет к аварийной остановке внешних скриптов.
  3. Накладные расходы на сетевые соединения: Создание COM-объекта ADODB.Connection при каждом открытии отчета требует ресурсов на авторизацию и рукопожатие с SQL-сервером, так как такие запросы не используют встроенный пул соединений кластера 1С.
  4. Отсутствие проверок и бизнес-логики: Прямой инсерт игнорирует платформенные механизмы ссылочной целостности, регистрацию изменений для планов обмена (РИБ) и подписки на события.

Способ 1. Использование метода Записать(Ложь)

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

Разберем пример правильного кода:


Набор = РегистрыСведений.РегСв.СоздатьНаборЗаписей();
// Метод Прочитать() НЕ вызываем!

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

// Параметр Замещать = Ложь отключает удаление старых записей
Набор.Записать(Ложь);

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

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

Способ 2. Запись через МенеджерЗаписи

Посмотрим на альтернативный штатный инструмент для одиночной вставки — МенеджерЗаписи. Этот объект ориентирован на атомарную работу со строкой независимого регистра сведений:


Менеджер = РегистрыСведений.РегСв.СоздатьМенеджерЗаписи();

// Заполняем измерения (первичный ключ)
Менеджер.Период = ТекущаяДатаСеанса();
Менеджер.ИмяОтчета = ИмяОтчета;
Менеджер.Пользователь = ПараметрыСеанса.ТекущийПользователь;

// Заполняем ресурсы и реквизиты
Менеджер.Включен = 1;

// Выполняем точечную запись
Менеджер.Записать(Истина);

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

Способ 3. Оптимизация структуры измерений и блокировок

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

  1. Вынесение лишних измерений в реквизиты: Если составные измерения вызывают конкуренцию, сделаем единственным измерением уникальный идентификатор (УникальныйИдентификатор типа Строка), а пользователя, дату и имя объекта перенесем в реквизиты. Это гарантирует отсутствие пересечений по первичному ключу.
  2. Перевод на управляемый режим блокировок: В свойствах конфигурации или конкретного регистра сведений установим режим использования блокировок Управляемый. Это переводит СУБД в режим версионирования строк (RCSI), полностью устраняя конфликты между читающими и пишущими потоками.
  3. Использование штатного журнала регистрации: Если целью является исключительно логирование фактов открытия отчетов, оптимальнее использовать не регистры сведений, а процедуру ЗаписьЖурналаРегистрации(). Она специально оптимизирована для фонового неблокирующего сбора событий аудита.
← На главную