В процессе разработки высоконагруженных систем на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с критической ошибкой: «Конфликт блокировок при выполнении транзакции: Неустранимый конфликт блокировок». Особенно остро эта проблема проявляется при работе с регистрами сведений, которые используются для интеграций (например, с Битрикс24), где множество документов одновременно пытаются записать данные в одну таблицу. Рассмотрим подробно, почему возникает эта ошибка и как правильно спроектировать механизм записи, чтобы избежать дедлоков.
Проанализируем классическую ситуацию. Когда два сеанса одновременно выполняют чтение и последующую запись в один и тот же диапазон данных регистра, происходит следующее:
В результате возникает ситуация, когда оба процесса вечно ждут друг друга. Система управления базами данных (СУБД), которую полезно анализировать глазами 1С-ника, обнаруживает этот цикл и принудительно завершает одну из транзакций с ошибкой «Неустранимый конфликт».
Главное правило работы в режиме управляемых блокировок: блокируй данные на запись до того, как начнешь их читать для анализа. Если мы сначала прочитаем данные методом Прочитать() или обычным запросом, СУБД наложит разделяемую блокировку, которую потом будет крайне сложно «повысить» до исключительной без риска дедлока.
Рассмотрим правильный алгоритм действий:
// 1. Создаем объект блокировки
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ИмяВашегоРегистра");
// 2. Устанавливаем режим Исключительный СРАЗУ
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// 3. Указываем конкретные измерения, которые будем анализировать
// Это крайне важно, чтобы не блокировать весь регистр целиком
ЭлементБлокировки.УстановитьЗначение("Объект", ТекОбъект);
ЭлементБлокировки.УстановитьЗначение("НастройкаПодключения", ТекНастройка);
// 4. Блокируем данные в транзакции
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
// Теперь, когда мы "застолбили" право на изменение этих строк, можно читать
НаборЗаписей = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
// Устанавливаем отборы, идентичные блокировке
НаборЗаписей.Отбор.Объект.Установить(ТекОбъект);
НаборЗаписей.Прочитать();
// Выполняем логику проверки и добавления записей
// ...
НаборЗаписей.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Часто данные для регистрации приходят в виде таблицы значений (ТЗ). Если использовать ИсточникДанных в объекте БлокировкаДанных, платформа автоматически заблокирует все строки из этой таблицы. Однако здесь кроется ловушка: если колонки ТЗ не типизированы (имеют тип Произвольный), блокировка может работать некорректно или вызывать избыточное блокирование.
Разберем, как правильно типизировать таблицу, используя метаданные самого регистра. Это позволит избежать «хардкода» типов и обеспечит максимальную точность блокировки.
// Получаем описания типов напрямую из метаданных регистра
ТипОбъекта = Метаданные.РегистрыСведений.ИмяРегистра.Измерения.Объект.Тип;
ТипНастройки = Метаданные.РегистрыСведений.ИмяРегистра.Измерения.НастройкаПодключения.Тип;
// Создаем и типизируем таблицу
ПакетДанныхДляЗаписи = Новый ТаблицаЗначений;
ПакетДанныхДляЗаписи.Колонки.Добавить("Объект", ТипОбъекта);
ПакетДанныхДляЗаписи.Колонки.Добавить("НастройкаПодключения", ТипНастройки);
// Заполняем таблицу данными из ПакетаДанных
// ...
// Теперь используем эту ТЗ как источник для блокировки
БлокировкаДанных = Новый БлокировкаДанных;
ЭлементБлокировкиДанных = БлокировкаДанных.Добавить("РегистрСведений.ИмяРегистра");
ЭлементБлокировкиДанных.ИсточникДанных = ПакетДанныхДляЗаписи;
// Указываем соответствие колонок ТЗ и измерений регистра
ЭлементБлокировкиДанных.ИспользоватьИзИсточникаДанных("Объект", "Объект");
ЭлементБлокировкиДанных.ИспользоватьИзИсточникаДанных("НастройкаПодключения", "НастройкаПодключения");
ЭлементБлокировкиДанных.Режим = РежимБлокировкиДанных.Исключительный;
НачатьТранзакцию();
БлокировкаДанных.Заблокировать();
// Далее следует безопасная запись данных
Рассмотрим ситуацию, когда нам нужно записать только те данные, которых еще нет в регистре. Вместо того чтобы читать весь набор записей в память и перебирать его в цикле (что медленно и ресурсоемко), эффективнее использовать запрос. Посмотрим на пример кода, который объединяет проверку существования и блокировку.
Сначала мы помещаем входящий пакет данных во временную таблицу, затем левым соединением проверяем наличие записей в регистре. В результат запроса попадут только те строки, которых в БД еще нет (ГДЕ Регистр.Объект ЕСТЬ NULL).
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ПакетДанных", ПакетДанных);
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Пакет ИЗ &ПакетДанных КАК Пакет;
|ВЫБРАТЬ Пакет.* ИЗ ВТ_Пакет КАК Пакет
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ИмяРегистра КАК РеалРегистр
|ПО Пакет.Объект = РеалРегистр.Объект
|И Пакет.Настройка = РеалРегистр.Настройка
|ГДЕ РеалРегистр.Объект ЕСТЬ NULL";
РезультатЗапроса = Запрос.Выполнить();
// Если есть что записывать
Если Не РезультатЗапроса.Пустой() Тогда
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрСведений.ИмяРегистра");
Элемент.ИсточникДанных = РезультатЗапроса;
Элемент.ИспользоватьИзИсточникаДанных("Объект", "Объект");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
МенеджерЗаписи = РегистрыСведений.ИмяРегистра.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, Выборка);
МенеджерЗаписи.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
КонецПопытки;
КонецЕсли;
Проанализируем дополнительные нюансы, которые помогут минимизировать вероятность конфликтов:
Блокировка.Заблокировать() и ЗафиксироватьТранзакцию() не должно быть тяжелых вычислений, обращений к внешним сервисам (HTTP/Web-запросы) или ожидания ввода пользователя.НаборЗаписей.Прочитать() без фильтров. Если вы вызываете этот метод без предварительной установки Отбор, система пытается прочитать и заблокировать все записи регистра. Даже если регистр пуст, это создает огромную нагрузку на менеджер блокировок.Соблюдение этих правил и использование исключительных управляемых блокировок позволит полностью избавиться от «Неустранимых конфликтов» и обеспечить высокую параллельность работы пользователей в системе.