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