Может ли установка управляемых блокировок 1С предотвратить эскалацию блокировок СУБД?

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

При разработке и администрировании крупных информационных систем на платформе «1С:Предприятие 8» часто возникает проблема, когда при записи больших объемов данных (например, при проведении тяжелых документов вроде «Формы ввода бюджета», содержащих более 5 000 – 10 000 строк) СУБД блокирует всю таблицу регистра. Это явление называется эскалацией блокировок. Существует миф, что если предварительно установить управляемую блокировку 1С по измерениям регистра, то это поможет избежать блокировки всей таблицы в СУБД. В этой статье мы подробно разберем, почему это не так, как соотносятся два механизма блокировок и как эффективно решить проблему эскалации.

Взаимосвязь блокировок 1С и СУБД: разделяем мух и котлет

Для начала проанализируем архитектуру взаимодействия платформы 1С и системы управления базами данных (СУБД). Блокировки в 1С и блокировки в СУБД — это два абсолютно разных, независимых механизма, работающих на разных уровнях абстракции:

  1. Управляемые блокировки 1С функционируют на уровне сервера приложений 1С. Менеджер управляемых блокировок ничего не знает о физических таблицах СУБД, страницах данных или индексах. Он оперирует прикладными объектами: пространствами блокировок, метаданными (регистры, справочники), их измерениями и значениями (например, конкретный «Сценарий» или «Организация»).
  2. Блокировки СУБД работают на уровне физического хранилища данных. СУБД (например, MS SQL Server) ничего не знает о метаданных 1С и логике проведения документов. Она видит только SQL-запросы (INSERT, UPDATE, DELETE) и накладывает физические блокировки на строки таблицы (RID или KEY), страницы данных (PAG) или всю таблицу целиком (TAB).

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

Однако при записи данных (когда происходит запись набора в СУБД) ситуация кардинально меняется. Независимо от того, какие управляемые блокировки вы установили в коде 1С, сервер приложений обязан физически отправить данные в базу. СУБД для выполнения операций изменения данных (пишущие транзакции) обязана наложить монопольные X-блокировки на физические строки. И если этих строк слишком много, СУБД автоматически принимает решение об эскалации.

Что такое эскалация блокировок и почему она происходит?

Рассмотрим механизм эскалации на примере популярной СУБД MS SQL Server. Сведения о каждой заблокированной строке (KEY) хранятся в оперативной памяти сервера SQL и занимают около 96 байт. Если транзакция изменяет огромное количество строк, то под удержание индивидуальных блокировок тратится значительный объем RAM, а СУБД тратит много ресурсов процессора на их обслуживание.

Чтобы сэкономить ресурсы, в MS SQL Server встроен механизм автоматического укрупнения блокировок. Порог эскалации по умолчанию составляет 5 000 блокировок на одну таблицу в рамках одной инструкции. Как только транзакция пытается изменить более 5 000 строк в одной таблице за один шаг, MS SQL Server автоматически заменяет тысячи мелких блокировок строк на одну блокировку всей таблицы (TAB).

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

При этом у управляемых блокировок 1С тоже есть свой порог эскалации — он составляет 100 000 блокировок на одно пространство. Но, как мы видим, СУБД сдается гораздо раньше (на 5 000), поэтому управляемые блокировки физически не могут уберечь СУБД от эскалации, так как порог СУБД преодолевается значительно быстрее.

Особенности работы PostgreSQL

Стоит отметить важное различие между СУБД. Проблема эскалации блокировок при записи больших пакетов данных характерна в первую очередь для MS SQL Server. В СУБД PostgreSQL механизм хранения блокировок строк устроен иначе. Информация о транзакции, заблокировавшей строку, записывается непосредственно в заголовок самой строки в памяти/на диске (поля xmin и xmax). Поскольку оперативная память под транзакционную таблицу блокировок строк в PostgreSQL не расходуется так жестко, как в MS SQL, в этой СУБД отсутствует классическая эскалация блокировок от строки к таблице при массовой записи. Запись даже 50 000 строк в PostgreSQL не приведет к автоматической блокировке всей таблицы.

Как решить проблему эскалации в MS SQL Server?

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

Вариант 1. Настройка СУБД (Административные методы)

Если архитектура конфигурации не позволяет быстро переписать код, наиболее эффективным решением будет управление поведением блокировок непосредственно в MS SQL Server.

Способ А. Включение флага трассировки 1211 или 1224

Вы можете отключить эскалацию блокировок на уровне всего сервера MS SQL с помощью специальных флагов трассировки:

Для включения флага трассировки (например, 1211) на постоянной основе необходимо добавить параметр -T1211 в параметры запуска службы SQL Server в SQL Server Configuration Manager и перезапустить службу.

Способ Б. Точечное отключение эскалации для конкретной таблицы

Вместо отключения эскалации для всего сервера, мы можем отключить её для конкретной «проблемной» таблицы физического регистра. Для этого выполним в SQL Server Management Studio следующий запрос:

ALTER TABLE _AccumReg12345 SET (LOCK_ESCALATION = DISABLE);

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

Вариант 2. Оптимизация на уровне платформы и кода 1С

Если мы хотим решить проблему раз и навсегда архитектурными методами без вмешательства в настройки СУБД, нам следует пересмотреть логику записи данных.

Способ А. Разделение итогов регистров

Для регистров накопления, по которым ведется активная параллельная запись, обязательно должно быть включено свойство «Разделение итогов» (Split Totals). Это позволяет платформе записывать движения параллельно в разные физические строки таблицы итогов, что снижает конкуренцию транзакций за одни и те же ресурсы СУБД.

Способ Б. Порционная (пакетная) запись данных

Если документ или обработка записывает огромный объем данных, проанализируем возможность записи этих данных порциями (батчами). Если записывать данные пакетами строго менее 5 000 строк (например, по 3 000 строк) в одной транзакции, СУБД MS SQL гарантированно не перейдет к эскалации блокировок.

Рассмотрим пример реализации пакетной записи независимого регистра сведений:


// Пример порционной записи набора записей регистра
Процедура ЗаписатьДанныеПорциями(ТаблицаДанных)
    
    РазмерПорции = 3000;
    ВсегоСтрок = ТаблицаДанных.Количество();
    Индекс = 0;
    
    Пока Индекс < ВсегоСтрок Цикл
        
        НачалоТранзакции();
        Попытка
            
            Набор = РегистрыСведений.ЗначенияБюджетов.СоздатьНаборЗаписей();
            // Настраиваем отбор, если пишем в конкретный разрез
            Набор.Отбор.Сценарий.Установить(ВыбранныйСценарий);
            
            // Заполняем порцию данных
            Для Счетчик = 1 По РазмерПорции Цикл
                Если Индекс >= ВсегоСтрок Тогда
                    Прервать;
                КонецЕсли;
                
                СтрокаДанных = ТаблицаДанных[Индекс];
                НоваяСтрока = Набор.Добавить();
                ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных);
                
                Индекс = Индекс + 1;
            КонецЦикла;
            
            // Записываем порцию (в режиме Замещать = Ложь, чтобы не затереть предыдущие порции)
            Набор.Записать(Ложь);
            
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            ВызватьИсключение;
        КонецПопытки;
        
    КонецЦикла;
    
КонецПроцедуры

Способ В. Сокращение времени транзакции

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

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

  1. Выполним все расчеты, проверки остатков и подготовку таблицы движений в оперативной памяти до начала записи в базу данных.
  2. Саму транзакцию сделаем максимально «короткой»: открыли транзакцию, быстро записали готовый набор данных в СУБД, закрыли транзакцию. Время блокировки сократится с десятков секунд до миллисекунд, и другие пользователи просто не успеют заметить кратковременное монопольное удержание таблицы.

Резюме

Управляемые блокировки 1С не могут предотвратить физическую эскалацию блокировок на уровне СУБД MS SQL Server при записи больших пакетов данных. Для решения проблемы используйте точечное отключение эскалации таблицы на уровне СУБД (ALTER TABLE), глобальные флаги трассировки (1211 / 1224) или перепишите алгоритмы записи в 1С на порционную обработку данных с оптимизацией времени транзакции.

← На главную