Как устранить взаимоблокировки (deadlocks) в регистре «Бонусные баллы» при проведении документов в 1С:Розница?

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

В процессе работы высоконагруженных систем на базе 1С:Предприятие 8.3 и MS SQL Server часто возникает ситуация, когда документы (например, «Отчет о розничных продажах») перестают проводиться из-за ошибок транзакций. Одной из самых коварных проблем является взаимоблокировка (deadlock). В этом материале мы подробно разберем, почему возникают такие конфликты в регистре накопления БонусныеБаллы, как механизм разделения итогов влияет на стабильность системы и что нужно изменить в коде и настройках сервера, чтобы навсегда забыть об этой ошибке.

Анализируем причину конфликта: механизм «Разделения итогов»

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

Однако здесь кроется ловушка. Если в коде при записи набора записей не установлено свойство БлокироватьДляИзменения = Истина, 1С не выстраивает транзакции в очередь. В результате две и более транзакции одновременно «проваливаются» на уровень СУБД (MS SQL Server). Проанализируем, что происходит внутри SQL Server:

  1. SQL Server пытается обновить записи в таблице итогов (обычно это таблица с префиксом _AccumRegTotals).
  2. Вместо блокировки одной конкретной строки, SQL Server часто накладывает блокировку на страницу данных (размером 8 КБ) или на весь индекс.
  3. Если две транзакции пытаются обновить разные строки, которые физически оказались на одной странице памяти, возникает конфликт.
  4. Когда первая транзакция захватила страницу А и ждет страницу Б, а вторая — захватила Б и ждет А, SQL Server фиксирует Deadlock и принудительно завершает одну из транзакций.

Шаг 1: Программная установка управляемой блокировки

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

Разберем пример кода, который необходимо внедрить в процедуру проведения документа или в модуль набора записей регистра БонусныеБаллы. Мы будем использовать объект БлокировкаДанных, чтобы заблокировать только те баллы, которые относятся к конкретному дисконтному номеру или партнеру:


// Создаем объект блокировки
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.БонусныеБаллы");

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

// Накладываем блокировку перед записью движений
Блокировка.Заблокировать();

Также крайне важно проверить свойство коллекции движений. В модуле проведения документа, перед тем как выполнять запись, убедимся, что установлено БлокироватьДляИзменения. Рассмотрим, как это выглядит на практике:


Движения.БонусныеБаллы.БлокироватьДляИзменения = Истина;
Движения.БонусныеБаллы.Записать();

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

Шаг 2: Оптимизация SQL Server и обслуживание индексов

Иногда программных мер недостаточно, если база данных находится в «запущенном» состоянии. Проанализируем технические настройки MS SQL Server, которые напрямую влияют на вероятность возникновения взаимоблокировок.

Во-первых, обратим внимание на эскалацию блокировок (Lock Escalation). Если документ «Отчет о розничных продажах» содержит сотни строк, SQL Server может решить, что блокировать каждую строку по отдельности слишком дорого, и заблокирует всю таблицу целиком. Чтобы снизить вероятность этого, необходимо регулярно выполнять обслуживание индексов.

Во-вторых, проверим настройки параллелизма. Для работы с 1С рекомендуется устанавливать параметр Max Degree of Parallelism (MAXDOP) в значение 1. Если SQL Server пытается распараллелить запрос на обновление итогов, он может создать взаимоблокировку сам с собой (intra-query deadlock).

Шаг 3: Работа с «горячими» данными и разделением итогов

Рассмотрим ситуацию, когда регистр БонусныеБаллы перегружен записями. В «Рознице» этот регистр часто содержит тысячи «нулевых» остатков, которые SQL Server все равно вынужден учитывать при поиске и блокировке. Проанализируем способы решения:

  1. Регулярный пересчет итогов: Настройте регламентное задание на ежемесячный пересчет итогов. Это «схлопнет» записи и уменьшит физический размер таблиц _AccumRegTotals.
  2. Отключение разделения итогов: Если в вашей базе работает менее 50 одновременных пользователей, попробуйте отключить галку «Разрешить разделение итогов» в свойствах регистра. В средних системах это, парадоксально, может убрать дедлоки, так как 1С начнет жестко контролировать очередь транзакций.
  3. Отложенные движения: Если бизнес-процесс позволяет, выполнение движений по бонусным баллам можно вынести в фоновое задание. В этом случае основная транзакция чека или отчета завершится мгновенно, а баллы «догонят» остатки чуть позже, не блокируя работу кассиров.

Резюме

Подводя итог нашему разбору, отметим: взаимоблокировка — это всегда следствие того, что система управления базами данных не смогла разрешить порядок доступа к ресурсам. Для решения проблемы в 1С:Розница мы рекомендуем:

← На главную