Как устранить взаимоблокировку при выполнении запроса на чтение внутри транзакции в 1С?

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

В процессе многопользовательской работы в 1С разработчики нередко сталкиваются с ошибкой deadlocked on lock (взаимоблокировка на уровне СУБД) при выполнении обычного запроса на чтение через Запрос.Выполнить(). На первый взгляд кажется нелогичным, что операция чтения может блокироваться или вызывать клинч с параллельными процессами. Разберем детально, почему это происходит и как устранить данную проблему.

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

Причины возникновения взаимоблокировок при чтении

Для понимания корня проблемы выясним, как работают блокировки в СУБД:

  1. Автоматический и смешанный режим блокировок: В конфигурациях, где установлен автоматический или смешанный режим управления блокировками («Автоматический и управляемый»), для транзакций СУБД используется строгий уровень изоляции — Repeatable Read или Serializable. При чтении данных накладываются разделяемые блокировки (S-блокировки), которые удерживаются СУБД до самого конца транзакции.
  2. Эскалация и повышение уровня блокировки (S → X): Когда две параллельные транзакции одновременно читают одни и те же данные (или диапазоны страниц), обе получают S-блокировки. Затем обе транзакции пытаются выполнить запись или изменение в эти же таблицы. Для записи СУБД требует монопольную блокировку (X-блокировку), которая несовместима с S-блокировкой параллельной транзакции. Возникает классический дедлок: первая транзакция ждет освобождения S-блокировки второй, а вторая ждет первую.
  3. Сканирование таблиц и индексов (Index Scan): Если запрос содержит неоптимальные соединения, подзапросы со срезом последних без точного отбора по ключевым полям или обращается к неиндексированным полям, СУБД выполняет сканирование большого диапазона страниц. В результате блокируются не только нужные строки, но и соседние записи.
  4. Фиктивность вложенных транзакций в 1С: Вызовы НачатьТранзакцию() внутри других транзакций лишь увеличивают счетчик платформы. Физическая транзакция на уровне СУБД всегда одна. Все блокировки удерживаются от первого вызова НачатьТранзакцию() до финального ЗафиксироватьТранзакцию().

Способ 1. Повышение уровня блокировки при чтении (конструкция ДЛЯ ИЗМЕНЕНИЯ)

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

Рассмотрим, как это работает: если транзакция сразу захватывает ресурс с намерением изменения (на уровне СУБД запрашивается блокировка обновления U или монопольная X), то вторая транзакция сразу встает в очередь ожидания и не начинает свое чтение. Взаимная блокировка исключается.

В тексте запроса чтения перед началом модификации данных добавим ключевое слово ДЛЯ ИЗМЕНЕНИЯ:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Бюджет.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.БюджетПодразделения КАК Бюджет
    |ГДЕ
    |	Бюджет.Ссылка = &Ссылка
    |ДЛЯ ИЗМЕНЕНИЯ";

Запрос.УстановитьПараметр("Ссылка", ДокументСсылка);
Результат = Запрос.Выполнить();

Выполняем такой запрос в самом начале транзакции до чтения остальных связанных данных и объектов. Потоки будут выстраиваться в последовательную очередь без падений по дедлоку.

Способ 2. Вынос подготовительных запросов за пределы транзакции

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

  1. Выполняем все тяжелые выборки, чтение регистров и расчеты во временные структуры данных до открытия транзакции.
  2. Открываем транзакцию непосредственно перед записью данных.
  3. Записываем сформированные документы максимально быстро и фиксируем транзакцию.

Посмотрим на пример правильной организации кода:


// 1. Чтение и подготовка данных вне транзакции
Запрос = Новый Запрос;
Запрос.Текст = "..."; // Выборка данных и срезов
ТаблицаОпераций = Запрос.Выполнить().Выгрузить();

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

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

Если в дедлоке участвуют таблицы регистров сведений (в коде запроса видны конструкции _InfoRg... с MAX(_Period)), проверим индексацию:

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

Способ 4. Перевод конфигурации или объектов на управляемый режим блокировок

Наиболее фундаментальным решением проблем с взаимоблокировками при чтении является перевод конфигурации в режим Управляемый:

  1. В свойствах конфигурации или конкретных документов/регистров устанавливаем Режим управления блокировками данных = Управляемый.
  2. На уровне СУБД (MS SQL Server / PostgreSQL) для управляемого режима включается версионирование строк (RCSI). Операции чтения перестают накладывать разделяемые S-блокировки на уровне СУБД и читают данные из снимков (снапшотов), что полностью исключает конфликты между чтением и записью.
  3. Если требуется защитить логику от параллельной модификации одних и тех же данных, используем явный объект платформы БлокировкаДанных:

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

Способ 5. Сортировка записываемых объектов при пакетной обработке

Когда одна транзакция создает и записывает несколько связанных документов, важно соблюдать единый порядок блокировки ресурсов во всех параллельных потоках:

  1. Перед циклом записи упорядочиваем массив данных (например, по возрастанию ссылки, кода или идентификатора).
  2. Если все транзакции всегда блокируют ресурсы в строго одинаковом порядке (A → B → C), возникновение взаимного ожидания (дедлока) становится математически невозможным: вторая транзакция просто подождет освобождения первого ресурса в очереди.
← На главную