Почему возникает конфликт блокировок в MS SQL Server при проведении документов с управляемыми блокировками?

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

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

Microsoft SQL Server Native Client 11.0: Превышено время ожидания запроса на блокировку. HRESULT=80040E31, SQLSrvr: SQLSTATE=HYT00, state=33, Severity=10, native=1222, line=1

Разберем детально, почему так происходит, как работают уровни блокировок в связке «1С — MS SQL Server» и какие настройки метаданных приводят к взаимным ожиданиям.

Разница между управляемыми блокировками 1С и физическими блокировками СУБД

Проанализируем природу сообщения об ошибке. Код ошибки native=1222 (Lock request time out period exceeded) формируется непосредственно сервером Microsoft SQL Server. Это свидетельствует о следующем:

  1. Менеджер управляемых блокировок 1С не зафиксировал конфликта. Пространства блокировок по полю Объект у двух документов не пересеклись, и платформа разрешила обеим транзакциям продолжить выполнение.
  2. Транзакции столкнулись на физическом уровне таблиц или страниц данных внутри MS SQL Server, где сработал таймаут ожидания (по умолчанию в 1С он составляет 20 секунд).

Рассмотрим типовой фрагмент кода, с которого начинается подобная проблема:


Движения.рс_Рег1.Записывать = Истина;

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

Если после вызова метода Заблокировать() установить точку останова в первом сеансе, а во втором попытаться провести аналогичный документ с другими объектами, то второй сеанс «зависнет» и упадет по таймауту СУБД. Выясним, что именно вызывает эту блокировку в базе данных.

Причина 1: Свойство документа «Удаление движений» в значении «Удалять автоматически»

Обратим внимание на режим очистки движений документа. Если в свойствах документа на закладке «Движения» установлено значение Удалять автоматически (так называемая старая методика проведения), платформа выполняет следующие действия:

  1. В момент вызова проведения документа неявно открывается транзакция.
  2. До входа в процедуру ОбработкаПроведения платформа выполняет прямой SQL-запрос DELETE по всем регистрам, где документ выступает регистратором.
  3. Если таблица регистра содержит небольшое количество записей или на ней не обновлена статистика, оптимизатор MS SQL Server вместо точечного удаления по некластерному индексу выполняет сканирование всей таблицы (Table Scan или широкий Index Scan).
  4. При сканировании накладываются монопольные блокировки на диапазоны страниц или на всю таблицу целиком. Первый сеанс захватывает эти страницы, останавливается на точке останова, а второй сеанс не может выполнить даже начальный DELETE своих собственных старых движений.

Причина 2: Эскалация блокировок и блокировка смежных страниц (Page Locks)

MS SQL Server распределяет данные по страницам объемом 8 КБ. Если несколько записей разных документов попадают на одну физическую страницу данных или в один узел индекса, блокировка накладывается не только на конкретную строку, но и на всю страницу (PAG). Кроме того, если транзакция модифицирует множество строк, СУБД может автоматически повысить уровень блокировки со строк до таблицы (Lock Escalation).

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

Причина 3: Непонимание назначения управляемой блокировки

Ответим на ключевой вопрос архитектуры: для чего вообще накладывается явная блокировка?

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

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

Решение проблемы и оптимизация параллельной работы

Разберем по шагам, как устранить конфликты блокировок и повысить параллельность проведения документов:

  1. Переключите режим удаления движений.
    В свойствах документа укажите Удаление движений: Удалять автоматически при отмене проведения. Это переведет документ на новую методику проведения. В таком режиме платформа не выполняет предварительный DELETE перед началом проведения. Новые движения при вызове Записать() набора записей просто перезаписывают существующие, что кардинально снижает нагрузку на СУБД и исключает захват всей таблицы регистра.
  2. Исключите необоснованную установку блокировок.
    Если в процедуре ОбработкаПроведения нет чтения данных регистра (проверки остатков, сверки лимитов), удалите создание объекта БлокировкаДанных. Запись движений в управляемом режиме транзакции сама по себе обеспечит целостность на уровне СУБД.
  3. Проверьте разделение итогов у регистров.
    Для всех регистров накопления убедитесь, что в конфигураторе или программно включен флаг Разделение итогов. Механизм разделения итогов добавляет к итогам регистра дополнительное суб-поле (разделитель), позволяя разным сеансам одновременно записывать движения без ожидания пересчета одной общей строки итогов.
  4. Проверьте режим управления блокировкой данных.
    Убедитесь, что свойство Режим управления блокировкой данных установлено в значение Управляемый не только на уровне конфигурации или регистра, но и у самого проводящегося документа. В смешанном режиме конфигурации режим транзакции определяется именно объектом, который инициирует транзакцию.
  5. Не используйте точку останова для проверки параллельности.
    Искусственная остановка в отладчике замораживает физическую транзакцию в MS SQL Server. Любая попытка записи в ту же таблицу из другого сеанса приведет к таймауту ожидания через 20 секунд. Для корректной проверки конкурентного доступа используйте запуск нескольких сеансов под нагрузкой или замеряйте время выполнения с помощью технологического журнала.

Диагностика конфликтов на стороне MS SQL Server

Если таймаут native=1222 сохраняется в рабочей базе, выполните анализ ожиданий в MS SQL Server с помощью динамических административных представлений. Пока сеанс ожидает блокировку, выполните в среде SQL Server Management Studio запрос:


SELECT 
    wt.blocking_session_id AS [БлокирующийСеанс],
    wt.session_id AS [ОжидающийСеанс],
    wt.wait_type AS [ТипОжидания],
    wt.wait_duration_ms AS [ВремяОжидания_мс],
    tl.resource_type AS [ТипРесурса],
    OBJECT_NAME(tl.resource_associated_entity_id) AS [ИмяТаблицы]
FROM sys.dm_os_waiting_tasks wt
JOIN sys.dm_tran_locks tl ON wt.resource_address = tl.lock_owner_address
WHERE wt.wait_type LIKE 'LCK_%';

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

← На главную