Как устранить ошибку «Конфликт блокировок при выполнении транзакции» в 1С?

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

Разработчики в среде 1С часто сталкиваются с крайне неприятной ошибкой: «Конфликт блокировок при выполнении транзакции. Превышено максимальное время ожидания предоставления блокировки». Она может парализовать работу пользователей, заблокировать выполнение регламентных заданий и привести к экстренным перезапускам службы Агента сервера 1С:Предприятия.

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

Выясним причину: почему возникает конфликт блокировок?

Для начала разберем физическую суть конфликта. Когда платформа 1С выполняет запись объекта (например, методом Записать()), СУБД (MS SQL Server, PostgreSQL и др.) пытается наложить исключительную блокировку (X-lock) на конкретные строки таблиц, чтобы гарантировать целостность данных во время транзакции.

Если в этот же момент параллельный сеанс (другой пользователь или регламентное задание) читает или изменяет те же самые таблицы, возникает конфликт интересов. СУБД выстраивает процессы в очередь. Если первый процесс не успевает освободить ресурсы за отведенное время (по умолчанию в 1С это 20 секунд), второй процесс аварийно завершается с ошибкой превышения времени ожидания.

Рассмотрим основные факторы, которые резко увеличивают вероятность возникновения конфликта:

  1. Обращение через точку к реквизитам составного типа (Разыменование): Если реквизит (например, ОбъектЗадачи) имеет составной тип, включающий десятки видов документов, то при обращении к его свойствам через точку платформа генерирует SQL-запрос с огромным количеством соединений LEFT OUTER JOIN со всеми таблицами, входящими в этот тип.
  2. Эскалация блокировок (Lock Escalation): СУБД, видя тяжелый запрос со множеством соединений, может принять решение заблокировать не конкретные строки (Row-level lock), а целые страницы данных или даже таблицы целиком.
  3. Использование оператора ИЛИ в условиях запроса: Наличие ИЛИ мешает оптимизатору СУБД эффективно использовать индексы, что приводит к полному сканированию таблиц (Table Scan / Index Scan) и блокировке избыточного диапазона данных.
  4. Смешение интерактивного и фонового контекстов: Выполнение транзакционных действий (запись задач, проведение документов) одновременно в регламентных заданиях и при открытии списков или форм пользователями.

Шаг 1. Оптимизация запроса: избавляемся от обращения через точку

Проанализируем классический пример проблемного кода. Допустим, у нас есть задача, у которой реквизит ОбъектЗадачи имеет составной тип (может быть ссылкой на различные документы: «Реализация товаров», «Поступление товаров», «ЗаказКлиента» и т.д.). Нам нужно выбрать задачи, по которым связанные документы проведены или помечены на удаление.

Посмотрим на пример неоптимального запроса:


ВЫБРАТЬ
    Ссылка КАК ЗадачаСсылка
ИЗ
    Задача.ЗадачаИсполнителя КАК Задача
ГДЕ
    Задача.Выполнена = ЛОЖЬ
    И (Задача.ОбъектЗадачи.Проведен = ИСТИНА 
        ИЛИ Задача.ОбъектЗадачи.ПометкаУдаления = ИСТИНА)

При выполнении этого запроса платформа 1С будет вынуждена соединить таблицу задач со всеми таблицами документов, которые теоретически могут находиться в поле ОбъектЗадачи. Это порождает колоссальные избыточные блокировки на СУБД.

Для исправления этой ситуации применим оператор ВЫРАЗИТЬ. Мы должны явно указать платформе, к какому конкретно типу документа мы обращаемся. Если бизнес-логика предполагает работу только с определенными типами документов, мы жестко ограничиваем их перечень:


ВЫБРАТЬ
    Задача.Ссылка КАК ЗадачаСсылка
ИЗ
    Задача.ЗадачаИсполнителя КАК Задача
ГДЕ
    Задача.Выполнена = ЛОЖЬ
    И (ВЫРАЗИТЬ(Задача.ОбъектЗадачи КАК Документ.РеализацияТоваровУслуг).Проведен = ИСТИНА
        ИЛИ ВЫРАЗИТЬ(Задача.ОбъектЗадачи КАК Документ.РеализацияТоваровУслуг).ПометкаУдаления = ИСТИНА)

Благодаря использованию ВЫРАЗИТЬ, СУБД выполнит соединение только с одной таблицей — «Реализация товаров и услуг», проигнорировав остальные типы документов. Диапазон блокировок существенно сузится.

Шаг 2. Избавление от оператора ИЛИ в условиях запроса

Даже после явного приведения типов оператор ИЛИ в условии запроса может провоцировать неоптимальные планы выполнения на уровне СУБД. Проанализируем, как переписать запрос, заменив ИЛИ на объединение подзапросов с помощью ОБЪЕДИНИТЬ ВСЕ.

Рассмотрим оптимальную структуру запроса:


ВЫБРАТЬ
    Задача.Ссылка КАК ЗадачаСсылка
ИЗ
    Задача.ЗадачаИсполнителя КАК Задача
ГДЕ
    Задача.Выполнена = ЛОЖЬ
    И ВЫРАЗИТЬ(Задача.ОбъектЗадачи КАК Документ.РеализацияТоваровУслуг).Проведен = ИСТИНА

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Задача.Ссылка КАК ЗадачаСсылка
ИЗ
    Задача.ЗадачаИсполнителя КАК Задача
ГДЕ
    Задача.Выполнена = ЛОЖЬ
    И ВЫРАЗИТЬ(Задача.ОбъектЗадачи КАК Документ.РеализацияТоваровУслуг).ПометкаУдаления = ИСТИНА

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

Шаг 3. Добавление индексов для ускорения поиска

Чтобы СУБД могла быстро находить нужные строки без сканирования всей таблицы, необходимо индексировать поля, участвующие в условиях отбора — поможет анализ объема данных и структуры индексов таблиц 1С. В нашем случае критически важным является поле Выполнена таблицы задач.

Выполним индексирование реквизита:

  1. Откроем конфигуратор 1С.
  2. Найдем объект метаданных (например, задачу ЗадачаИсполнителя).
  3. Перейдем на вкладку «Реквизиты» (или в свойства стандартных реквизитов).
  4. Найдем реквизит Выполнена.
  5. В палитре свойств установим значение свойства «Индексировать» в положение «Индексировать» (или «Индексировать с дополнительным упорядочиванием», если требуется сортировка).

Шаг 4. Оптимальное архитектурное решение (Декуплинг)

Хотя оптимизация запросов через ВЫРАЗИТЬ и избавление от ИЛИ дают быстрый результат, самым надежным и профессиональным решением является изменение архитектуры хранения данных — для этого пригодится архитектурный анализ конфигураций и графа зависимостей объектов 1С.

Постоянное чтение реквизитов связанных документов из задач — это архитектурная ошибка. Вместо этого мы можем дублировать необходимые состояния (например, признак ДокументПроведенИлиУдален) непосредственно в саму задачу при записи связанного документа.

Реализуем этот механизм по шагам:

  1. Добавим в задачу ЗадачаИсполнителя новый реквизит ОбъектНеактивен с типом Булево и включим для него свойство «Индексировать».
  2. В модуле объекта связанных документов (или в общем модуле в подписке на события ПередЗаписью или ПриЗаписи документов) добавим код, который при изменении состояния проведения или пометки удаления автоматически находит незакрытые задачи по этому документу и обновляет в них реквизит ОбъектНеактивен — для этого есть инструмент отладки кода для поиска ошибок в режиме Предприятия.

Посмотрим на пример кода в подписке на событие при записи документа:


Процедура ОбновитьСтатусЗадачДокумента(Источник, Отказ) Экспорт
    
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    // Проверим, изменилось ли состояние
    СостояниеИзменилось = Источник.Проведен ИЛИ Источник.ПометкаУдаления;
    
    Если СостояниеИзменилось Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |	Задача.Ссылка КАК Ссылка
            |ИЗ
            |	Задача.ЗадачаИсполнителя КАК Задача
            |ГДЕ
            |	Задача.ОбъектЗадачи = &ДокументСсылка
            |	И Задача.Выполнена = ЛОЖЬ";
        
        Запрос.УстановитьПараметр("ДокументСсылка", Источник.Ссылка);
        Выборка = Запрос.Выполнить().Выбрать();
        
        Пока Выборка.Следующий() Цикл
            ЗадачаОбъект = Выборка.Ссылка.ПолучитьОбъект();
            ЗадачаОбъект.ОбъектНеактивен = ИСТИНА;
            Попытка
                ЗадачаОбъект.Записать();
            Исключение
                // Обработка исключения при параллельной блокировке
                ЗаписьЖурналаРегистрации("ОбновлениеЗадач", УровеньЖурналаРегистрации.Предупреждение, , , ОписаниеОшибки());
            КонецПопытки;
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

После реализации этой архитектурной схемы наш периодический запрос в регламентном задании станет максимально простым, быстрым и изолированным. Он будет обращаться только к одной таблице задач без каких-либо соединений:


ВЫБРАТЬ
    Задача.Ссылка КАК ЗадачаСсылка
ИЗ
    Задача.ЗадачаИсполнителя КАК Задача
ГДЕ
    Задача.Выполнена = ЛОЖЬ
    И Задача.ОбъектНеактивен = ИСТИНА

Этот запрос отработает за миллисекунды и гарантированно не вызовет конфликтов блокировок в СУБД.

Шаг 5. Настройка управляемых блокировок

Если база данных все еще работает в автоматическом режиме блокировок (свойство конфигурации «Режим управления блокировкой данных» установлено в «Автоматический»), СУБД использует максимальный уровень изоляции транзакций SERIALIZABLE. В этом режиме транзакции блокируют избыточные диапазоны строк, что делает дедлоки неизбежными.

Для кардинального решения проблемы переведем базу данных в Управляемый режим блокировок:

  1. Откроем свойства корневого элемента конфигурации.
  2. Найдем свойство «Режим управления блокировкой данных».
  3. Установим значение в «Управляемый».

В управляемом режиме СУБД работает на уровне изоляции READ COMMITTED (с включенным snapshot-изолированием). Чтение данных запросами больше не накладывает блокировок СУБД, а транзакционные блокировки контролируются самой платформой 1С через объект БлокировкаДанных.

Посмотрим на пример явного наложения управляемой блокировки в коде перед записью объекта:


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

Попытка
    Блокировка.Заблокировать();
    // Производим чтение и последующую запись объекта
    ЗадачаОбъект = СсылкаНаЗадачу.ПолучитьОбъект();
    ЗадачаОбъект.Выполнена = ИСТИНА;
    ЗадачаОбъект.Записать();
Исключение
    // Обработка ситуации, если объект уже заблокирован параллельным процессом
КонецПопытки;

Шаг 6. Диагностика конфликтов блокировок через Технологический журнал

Если блокировки продолжают возникать, и вы не можете определить их точный источник, необходимо настроить сбор Технологического журнала (ТЖ) 1С — для этого подойдёт анализ производительности 1С на основе техжурнала и СУБД. Это позволит точно зафиксировать транзакцию-«виновника» и транзакцию-«жертву».

Создадим файл конфигурации ТЖ logcfg.xml в папке настроек сервера 1С (обычно C:\Program Files\1cv8\conf\) со следующим содержимым:


<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\v8logs\locks" history="24">
        <event>
            <eq property="Name" value="TLOCK"/>
        </event>
        <event>
            <eq property="Name" value="TTIMEOUT"/>
        </event>
        <event>
            <eq property="Name" value="TDEADLOCK"/>
        </event>
        <property name="all"/>
    </log>
</config>

После возникновения ошибки проанализируем файлы логов в каталоге C:\v8logs\locks. Событие TTIMEOUT покажет точное время конфликта, а параметры WaitConnections укажут номера сеансов, которые удерживали блокировку и мешали выполнению вашей транзакции.

Мы подробно разобрали причины возникновения блокировок при работе с транзакциями в 1С и методы их устранения. Применяя на практике типизацию составных типов через ВЫРАЗИТЬ, избавление от сложных условий с ИЛИ, а также декуплинг данных, вы сможете обеспечить стабильную, быструю и бесконфликтную работу вашей информационной системы даже при высокой интенсивности параллельных процессов.

← На главную