Почему возникает конфликт блокировок при чтении регистра и записи документа в транзакции 1С

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

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

Посмотрим на типичную схему кода, вызывающую проблему:


НачатьТранзакцию();

// Чтение данных из регистра
Запрос = Новый Запрос("ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах ГДЕ ...");
Результат = Запрос.Выполнить();

// При записи и проведении документа возникает конфликт
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);

ЗафиксироватьТранзакцию();

Проанализируем причины такого поведения системы, механизм взаимодействия блокировок на уровне СУБД и рассмотрим эффективные варианты исправления данной ошибки.

Причины возникновения конфликтов блокировок

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

Рассмотрим, что происходит на уровне СУБД в зависимости от режима блокировок:

  1. Автоматический режим блокировок (режим Repeatable Read в СУБД): при выполнении чтения запросом СУБД накладывает разделяемую блокировку (S-блокировку) на считываемые записи до конца транзакции. Когда далее вызывается метод Записать(), система пытается установить исключительную блокировку (X-блокировку) на те же или смежные таблицы. Если в этот момент другой параллельный сеанс выполняет аналогичную операцию, возникает классическая взаимоблокировка (Deadlock): оба сеанса удерживают S-блокировку и ждут освобождения ресурса для наложения X-блокировки.
  2. Управляемый режим блокировок (режим Read Committed / Read Committed Snapshot): запрос налагает S-блокировку только на время своего выполнения либо читает данные из снимка (snapshot). Ошибка блокировки может возникать не при чтении, а в момент вызова проведения, если порядок захвата ресурсов в параллельных транзакциях несогласован.
  3. Несогласованный порядок захвата ресурсов: если первая транзакция сначала запрашивает регистр, а затем документ, а вторая транзакция (например, проведение документа другим пользователем) сначала заблокировала документ, а затем пытается записать движения в регистр, возникает конфликт из-за пересечения порядка блокировки таблиц.
  4. Эскалация блокировок и таблицы итогов: при записи движений регистра система обновляет таблицы итогов. Если в регистрах накопления отключено разделение итогов или запрос блокирует широкий диапазон данных из-за отсутствия индексов, происходит эскалация блокировки до уровня всей таблицы.

Способы решения проблемы

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

Вариант 1. Вынос чтения за пределы явной транзакции

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


// Запрос выполняется до открытия транзакции
Запрос = Новый Запрос("ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах ГДЕ ...");
Результат = Запрос.Выполнить();

НачатьТранзакцию();
Попытка
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

В этом случае при вызове запроса S-блокировка на уровне СУБД либо накладывается кратковременно на время считывания, либо не накладывается вовсе, что полностью предотвращает удерживание разделяемых блокировок во время проведения.

Вариант 2. Использование инструкции ДЛЯИЗМЕНЕНИЯ в запросе

Если конфигурация работает в автоматическом режиме блокировок и запрос обязательно должен находиться внутри транзакции, необходимо сразу запрашивать исключительную блокировку. Для этого в языке запросов 1С используется конструкция ДЛЯИЗМЕНЕНИЯ.


НачатьТранзакцию();
Попытка
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Товары.Номенклатура,
        |   Товары.Количество
        |ИЗ
        |   РегистрНакопления.ТоварыНаСкладах КАК Товары
        |ДЛЯИЗМЕНЕНИЯ"; // Предотвращает получение S-блокировки, сразу накладывая X/U-блокировку
    
    Результат = Запрос.Выполнить();
    
    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Благодаря инструкции ДЛЯИЗМЕНЕНИЯ СУБД сразу накладывает блокировку обновления (U-блокировку) или исключительную (X-блокировку), не создавая потенциального тупика с S-блокировками при параллельной работе.

Вариант 3. Использование объекта БлокировкаДанных в управляемом режиме

В управляемом режиме блокировок лучшей практикой является явная установка блокировок через объект БлокировкаДанных перед вызовом чтения или проведения. Блокировка должна накладываться с точным указанием отборов по измерениям.


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

    // Теперь чтение и запись защищены управляемой блокировкой 1С
    Запрос = Новый Запрос("ВЫБРАТЬ * ИЗ РегистрНакопления.ТоварыНаСкладах ГДЕ Склад = &Склад");
    Запрос.УстановитьПараметр("Склад", СкладСсылка);
    Результат = Запрос.Выполнить();

    ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ВызватьИсключение;
КонецПопытки;

Дополнительные рекомендации по предотвращению конфликтов

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