Взаимные блокировки (дедлоки) — одна из наиболее частых и сложных проблем производительности в крупных базах на платформе 1С:Предприятие 8, особенно когда речь идет об устаревших или сильно доработанных конфигурациях, таких как 1С:Управление производственным предприятием (УПП) ред. 1.3. Ситуация усугубляется, если в системе используется РАУЗ (Расширенная аналитика учета затрат), алгоритмы которой активно работают с аналитическими разрезами в транзакциях проведения документов.
В этой статье мы подробно разберем природу возникновения дедлоков, рассмотрим практические "лайфхаки" для их локализации и устранения, а также найдем архитектурные решения для распространенных сценариев блокировок при записи кастомных данных.
---
Прежде чем переходить к инструментам анализа, давайте вспомним, почему СУБД вообще принимает решение аварийно завершить одну из транзакций по ошибке Deadlock. Существует две основные классические причины возникновения взаимных блокировок:
В управляемом режиме блокировок платформа 1С пытается минимизировать влияние СУБД, однако если разработчик не накладывает блокировки явно в коде, платформа генерирует их автоматически: накладывает разделяемые блокировки при чтении через объектные методы или наборы записей и исключительные блокировки при записи данных.
---
В РАУЗ дедлоки часто выглядят пугающе — сервисы анализа (например, ЦУП или сервис Гилева) могут показывать графы взаимоблокировок, в которых участвуют десятки сеансов (так называемые циклические многопоточные дедлоки). Разберем причины их появления и методы анализа по шагам.
Причина 1: Динамическое создание ключей аналитики. При проведении документов РАУЗ обращается к независимым регистрам сведений (например, АналитикаВидаУчета, АналитикаУчетаЗатрат). Если система не находит нужный ключ аналитики, она пытается создать его прямо внутри транзакции проведения. Когда параллельно проводятся десятки документов, разные сеансы сначала читают регистр (накладывая S-lock), не находят ключ и одновременно пытаются записать новую запись. Происходит массовое повышение уровня блокировки и дедлок.
Причина 2: Эскалация блокировок (Lock Escalation) на уровне СУБД. Если документ делает очень много движений по регистру накопления УчетЗатратРегл (более 5000 строк), MS SQL Server автоматически переводит блокировку отдельных строк (Key/Row) на уровень всей таблицы (Table Lock). Из-за этого все параллельные процессы проведения мгновенно выстраиваются в очередь, что лавинообразно приводит к таймаутам и дедлокам.
Как быстро локализовать проблему:
TDEADLOCK и TLOCK. ТЖ предоставит точный SQL-запрос, имя таблицы БД (например, _InfoRgXXXX) и, самое главное, стек вызова встроенного языка 1С, на котором произошел сбой.xml_deadlock_report. Это позволит получить наглядную XML-схему (граф дедлока), где будет четко видно, какие именно индексы таблиц и типы блокировок вошли в клинч.---
Один из самых простых и эффективных способов избавиться от 90% дедлоков типа "чтение-запись" без изменения конфигурации — это включение режима RCSI (Read Committed Snapshot Isolation) на MS SQL Server.
При включенном RCSI операции чтения больше не требуют наложения разделяемых блокировок (S-lock). Вместо этого читающие транзакции получают согласованную копию данных из системной базы данных tempdb. Читатели больше не блокируют писателей, а писатели не мешают читателям.
Для включения этого режима выполните следующий скрипт в MS SQL Server Management Studio (база 1С должна быть в монопольном режиме):
ALTER DATABASE [Имя_Вашей_Базы] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [Имя_Вашей_Базы] SET READ_COMMITTED_SNAPSHOT ON;
ALTER DATABASE [Имя_Вашей_Базы] SET MULTI_USER;
Важно: Этот метод не является "серебряной пулей". Он отлично решает конфликты чтения-записи, но бессилен против конфликтов "запись-запись" (когда две транзакции одновременно пытаются обновить одну и ту же строку). Кроме того, RCSI увеличивает нагрузку на tempdb, поэтому убедитесь, что файлы tempdb расположены на быстрых SSD/NVMe дисках.
---
Рассмотрим практическую задачу из сообщения форума. Имеется независимый регистр сведений, в который перед записью документа выполняется чтение состояния некоторого ресурса (например, Тара), а затем в обработке проведения выполняется запись нового состояния. При одновременном проведении документов по одной и той же таре возникает дедлок.
Если мы просто установим управляемую исключительную блокировку на измерение Тара перед чтением, мы устраним дедлок, но получим другую проблему — огромные ожидания на блокировках ("бутылочное горлышко"). Разберем, как решить эту проблему архитектурно.
Если в вашем коде запись в независимый регистр сведений выполняется через объект РегистрСведенийНаборЗаписей, платформа при записи очищает старый набор и пишет новый. Это накладывает избыточные блокировки на диапазон значений.
Если вам нужно записать ровно одну строку, всегда используйте РегистрСведенийМенеджерЗаписи. Он работает точечно и накладывает более "мягкие" и гранулярные блокировки на уровне СУБД. Сравним подходы:
Неэффективно (через Набор записей):
Набор = РегистрыСведений.СостояниеТары.СоздатьНаборЗаписей();
Набор.Отбор.Тара.Установить(ВыбраннаяТара);
Набор.Отбор.Документ.Установить(СсылкаНаДокумент);
НоваяСтрока = Набор.Добавить();
НоваяСтрока.Тара = ВыбраннаяТара;
НоваяСтрока.Документ = СсылкаНаДокумент;
НоваяСтрока.Состояние = Перечисления.СостоянияТары.Принято;
Набор.Записать();
Эффективно (через Менеджер записи):
Менеджер = РегистрыСведений.СостояниеТары.СоздатьМенеджерЗаписи();
Менеджер.Тара = ВыбраннаяТара;
Менеджер.Документ = СсылкаНаДокумент;
Менеджер.Состояние = Перечисления.СостоянияТары.Принято;
Менеджер.Записать(Истина); // Запись с замещением
Если без чтения перед записью не обойтись, мы обязаны использовать управляемую блокировку в режиме РежимБлокировкиДанных.Исключительный перед выполнением запроса на чтение. Это превентивно заблокирует данные для других транзакций и предотвратит дедлок обновления (Lock Upgrade).
Но как избежать длинных очередей ожидания? Основной секрет — сортировка блокируемых ресурсов. Если в одном документе обрабатывается несколько единиц тары, они должны блокироваться строго в одном и том же порядке во всех транзакциях (например, по алфавиту или по возрастанию УИД ссылки). В этом случае транзакции выстроятся в аккуратную очередь и быстро обработают данные без дедлоков.
Рассмотрим пример установки правильной управляемой блокировки с сортировкой списка:
// Предположим, у нас есть МассивТары, полученный из табличной части документа
// Сначала обязательно сортируем список по Уникальному Идентификатору
ТаблицаТары = Новый ТаблицаЗначений;
ТаблицаТары.Колонки.Добавить("Ссылка");
ТаблицаТары.Колонки.Добавить("УИД", Новый ОписаниеТипов("Строка"));
Для Каждого Элемент Из МассивТары Цикл
НоваяСтрока = ТаблицаТары.Добавить();
НоваяСтрока.Ссылка = Элемент;
НоваяСтрока.УИД = Строка(Элемент.УникальныйИдентификатор());
КонецЦикла;
ТаблицаТары.Сортировать("УИД Возр");
// Теперь устанавливаем блокировку строго в отсортированном порядке
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СостояниеТары");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = ТаблицаТары;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Тара", "Ссылка");
// Блокируем данные перед выполнением чтения
Блокировка.Заблокировать();
// Теперь можно безопасно читать состояние тары запросом и производить запись
Если бизнес-логика позволяет, откажитесь от жесткой блокировки тары. Примените подход оптимистической блокировки:
"Состояние тары изменилось параллельным сеансом. Пожалуйста, повторите проведение документа".Это полностью разгрузит систему от очередей ожидания, так как параллельные изменения одной и той же тары в одну и ту же секунду на практике происходят крайне редко.
---
Если дедлоки происходят внутри типовых механизмов РАУЗ, обратите внимание на структуру индексов независимых аналитических регистров сведений. В старых релизах УПП порядок измерений в таких регистрах, как АналитикаУчетаПартий или АналитикаУчетаЗатрат, часто неоптимален.
Лайфхак по оптимизации регистров аналитики РАУЗ:
Перенесите наиболее заполненные и часто используемые измерения (например, Номенклатура, Склад, Затрата) на самые первые места в списке измерений регистра в конфигураторе. Измерения, которые часто остаются пустыми (например, Проект, ДоговорКомитента), переместите в самый конец списка.
Этот простой шаг заставит СУДБ перестроить кластерный индекс таблицы. Вместо сканирования всего индекса (Index Scan), которое часто приводит к избыточным блокировкам соседних строк и дедлокам, SQL Server начнет выполнять точечный поиск (Index Seek), что кардинально повысит параллелизм работы системы.