В процессе разработки систем аудита или логирования действий пользователей (например, фиксации открытия отчетов или обработок) программисты нередко сталкиваются с конфликтами блокировок. Типичная ситуация: в системе одновременно работают десятки или сотни пользователей, при каждом открытии отчета запускается процедура логирования, и база данных начинает зависать либо выдавать сообщения об ошибках взаимоблокировок или превышении времени ожидания транзакции.
В попытке обойти проблему возникает соблазн отказаться от платформенных механизмов и писать данные напрямую в таблицу СУБД с помощью ADODB и прямого оператора INSERT. Разберем, почему такая идея несет серьезные риски и как решить проблему конфликта блокировок штатными и безопасными средствами платформы 1С:Предприятие.
Проанализируем стандартную ошибку в коде, которая чаще всего провоцирует тотальную блокировку таблицы регистра сведений:
Набор = РегистрыСведений.РегСв.СоздатьНаборЗаписей();
Набор.Прочитать();
Запись = Набор.Добавить();
// заполнение полей
Набор.Записать();
Выясним причину проблем с этим кодом:
Набор.Прочитать() вызывается без установки отбора (Отбор.<ИмяИзмерения>.Установить(...)). Это означает, что платформа считывает из базы абсолютно все существующие строки регистра (десятки и сотни тысяч записей).Набор.Записать() по умолчанию параметр Замещать равен Истина. Платформа открывает неявную транзакцию, удаляет из базы все записи, соответствующие текущему отбору (а так как отбора нет — пытается очистить всю таблицу целиком), и записывает накопленный набор заново.SERIALIZABLE. А использование конструкций вроде ДЛЯ ИЗМЕНЕНИЯ накладывает блокировки диапазонов (Key-Range), парализуя соседние транзакции даже при работе с логически несвязанными данными.Посмотрим, к каким последствиям приводит выполнение прямого INSERT INTO в обход платформы 1С:
_InfoRg123) динамически генерируются платформой. При обновлении конфигурации, реструктуризации базы, выгрузке/загрузке файла .dt номера таблиц могут измениться, что приведет к аварийной остановке внешних скриптов.ADODB.Connection при каждом открытии отчета требует ресурсов на авторизацию и рукопожатие с SQL-сервером, так как такие запросы не используют встроенный пул соединений кластера 1С.Рассмотрим самое простое и эффективное решение, если в регистр требуется исключительно добавлять новые строки аудита. Для этого необходимо исключить считывание старых записей и указать платформе, что удалять существующие данные не нужно.
Разберем пример правильного кода:
Набор = РегистрыСведений.РегСв.СоздатьНаборЗаписей();
// Метод Прочитать() НЕ вызываем!
Запись = Набор.Добавить();
Запись.Период = ТекущаяДатаСеанса();
Запись.ИмяОтчета = ИмяОтчета;
Запись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
Запись.Включен = 1;
// Параметр Замещать = Ложь отключает удаление старых записей
Набор.Записать(Ложь);
В этом случае платформа не пытается удалять данные по таблице, а выполняет точечную быструю вставку новой строки, сводя время удержания транзакции к долям миллисекунды.
Важно: При использовании Набор.Записать(Ложь) уникальность ключевых полей ложится на плечи разработчика. Если комбинация измерений совпадет с уже имеющейся в базе, СУБД выбросит ошибку нарушения первичного ключа. Для исключения коллизий убедимся, что среди измерений есть поле с высокой точностью (дата и время с секундами) либо уникальный идентификатор.
Посмотрим на альтернативный штатный инструмент для одиночной вставки — МенеджерЗаписи. Этот объект ориентирован на атомарную работу со строкой независимого регистра сведений:
Менеджер = РегистрыСведений.РегСв.СоздатьМенеджерЗаписи();
// Заполняем измерения (первичный ключ)
Менеджер.Период = ТекущаяДатаСеанса();
Менеджер.ИмяОтчета = ИмяОтчета;
Менеджер.Пользователь = ПараметрыСеанса.ТекущийПользователь;
// Заполняем ресурсы и реквизиты
Менеджер.Включен = 1;
// Выполняем точечную запись
Менеджер.Записать(Истина);
Использование менеджера записи делает код лаконичным, прозрачным и не затрагивает параллельные строки других пользователей.
Если параллельный поток пользователей велик, разберем архитектурные улучшения структуры данных:
УникальныйИдентификатор типа Строка), а пользователя, дату и имя объекта перенесем в реквизиты. Это гарантирует отсутствие пересечений по первичному ключу.Управляемый. Это переводит СУБД в режим версионирования строк (RCSI), полностью устраняя конфликты между читающими и пишущими потоками.ЗаписьЖурналаРегистрации(). Она специально оптимизирована для фонового неблокирующего сбора событий аудита.