Как устранить ошибку «Превышено время ожидания запроса на блокировку» при параллельном проведении документов в 1С?

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

При одновременной работе нескольких пользователей в клиент-серверном варианте базы 1С на СУБД Microsoft SQL Server нередко возникает ошибка: Конфликт блокировок при выполнении транзакции: Microsoft SQL Server Native Client: Превышено время ожидания запроса на блокировку (native=1222). Разберем механизм появления этой ошибки, выясним причины и рассмотрим по шагам методы её устранения.

Почему возникает конфликт блокировок при проведении документов

Проанализируем ситуацию, происходящую внутри транзакции проведения документа. Когда транзакция открывается, платформа взаимодействует с MS SQL Server на определенном уровне изоляции транзакций:

  1. Чтение без отбора внутри транзакции. Если в процедуре ОбработкаПроведения выполняется запрос к табличной части документа или регистру без явного отбора по текущему документу (например, ВЫБРАТЬ Документы.Ссылка ИЗ Документ.ИмяДокумента.ИмяТабличнойЧасти КАК Документы), СУБД выполняет полное сканирование таблицы (Table Scan) или индекса (Index Scan).
  2. Автоматический режим блокировок и разделяемые S-блокировки. В автоматическом режиме управления блокировками чтение данных внутри транзакции накладывает разделяемые (Shared, S) блокировки на уровне СУБД. Эти блокировки удерживаются до момента завершения транзакции (COMMIT или ROLLBACK).
  3. Конфликт с операциями записи. Параллельная транзакция другого сеанса, пытающаяся записать свои данные в эту же таблицу или индекс, запрашивает монопольную (Exclusive, X) блокировку. Так как S и X блокировки несовместимы, второй сеанс переходит в состояние ожидания. Если первая транзакция удерживает блокировку дольше установленного таймаута (обычно от 20 до 40 секунд), второй сеанс падает с ошибкой превышения времени ожидания.

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

Способ 1. Оптимизация запросов и использование отборов по ссылке

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

Рассмотрим правильный вариант передачи ссылки текущего документа в параметрах запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТабЧасть.Номенклатура КАК Номенклатура,
    |	ТабЧасть.Количество КАК Количество
    |ИЗ
    |	Документ.РегистрацияПотребленияУслуг.ПотреблениеУслугПоОбъектам КАК ТабЧасть
    |ГДЕ
    |	ТабЧасть.Ссылка = &ТекущийДокумент";

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

Кроме того, если данные табличной части уже загружены в память объекта при проведении, обратимся к ним напрямую через коллекцию ЭтотОбъект.ПотреблениеУслугПоОбъектам, полностью исключив обращение к СУБД через тяжелые SQL-запросы.

Способ 2. Перевод базы данных на управляемый режим блокировок и Read Committed Snapshot

Разберем фундаментальное архитектурное решение платформы 1С:Предприятие 8.3 — использование режима Управляемых блокировок в связке с версионированием строк на стороне MS SQL Server.

  1. В конфигураторе откроем свойства конфигурации и установим свойство «Режим управления блокировкой данных» в значение «Управляемый» (или установим данный режим для конкретного документа).
  2. На стороне СУБД Microsoft SQL Server включим версионирование строк READ_COMMITTED_SNAPSHOT. Для этого выполним в SQL Server Management Studio следующий скрипт:

ALTER DATABASE [ИмяБазыДанных] 
SET READ_COMMITTED_SNAPSHOT ON 
WITH ROLLBACK IMMEDIATE;

В таком режиме операции чтения в транзакциях перестают накладывать разделяемые S-блокировки на уровне SQL Server. Читающие сеансы получают зафиксированные версии строк из базы данных tempdb, благодаря чему читающие транзакции не блокируют пишущие, а пишущие транзакции не блокируют читающие.

Способ 3. Явная установка управляемых блокировок данных

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

Посмотрим на пример использования объекта БлокировкаДанных:


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

Блокировка.Заблокировать();

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

Способ 4. Сокращение длительности транзакций

Проанализируем время удержания транзакций. Чем дольше выполняется процедура ОбработкаПроведения, тем выше вероятность конфликта. Применим следующие рекомендации:

  1. Вынесем предварительные проверки заполнения реквизитов, прав доступа и сложных внешних расчетов в модуль формы или обработчик ПередЗаписью до открытия транзакции проведения.
  2. Категорически исключим внутри транзакции вызовы внешних веб-сервисов, интеграций, работу с файлами и паузы ожидания.
  3. Выполним запись движений по регистрам пакетом в конце транзакции проведения, предварительно отключив автоматическую запись движений через свойство Движения.ИмяРегистра.Записывать = Истина.
← На главную