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