Как победить автоматические блокировки таблиц СУБД при обмене данными в РИБ 1С?

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

При работе с распределенными информационными базами (РИБ) разработчики и системные администраторы часто сталкиваются со следующей проблемой: во время выполнения сеанса загрузки и выгрузки данных на таблицы базы данных накладываются жесткие автоматические и транзакционные блокировки. В результате пользователи в периферийных или центральной базах вынуждены простаивать («курить») до тех пор, пока процесс обмена полностью не завершится. Иногда это занимает 10 и более минут, что абсолютно недопустимо для систем, работающих в режиме 24/7.

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

Почему возникают блокировки при обмене РИБ?

Давайте выясним физическую природу проблемы. Когда запускается стандартный процесс обмена, платформа 1С выполняет метод ПланыОбмена.ВыбратьИзменения(). В этот момент на уровне СУБД (MS SQL Server или PostgreSQL) происходят следующие процессы:

  1. Выполняется чтение из таблиц регистрации изменений (в СУБД они имеют префикс _ChangeRec).
  2. Платформа проставляет номер отправленного сообщения для каждого зарегистрированного объекта. Для этого выполняется инструкция UPDATE к таблицам изменений.
  3. Для выполнения UPDATE СУБД накладывает эксклюзивные (X) блокировки на строки таблицы изменений.
  4. Параллельно сеанс обмена начинает считывать сами изменяемые данные из основных физических таблиц (документы, справочники, регистры сведений), накладывая на них разделяемые (S) блокировки.

В это же время обычные пользователи продолжают активно работать: проводить документы, записывать справочники. Их транзакции пишут данные в основные таблицы (накладывая X-блокировки) и пытаются зарегистрировать эти изменения в таблицах _ChangeRec. Возникает классический Deadlock (взаимоблокировка): сеанс обмена удерживает таблицу изменений и ожидает доступ к основной таблице, а сеанс пользователя удерживает основную таблицу и ждет освобождения таблицы изменений.

Если объем передаваемых данных велик, СУБД автоматически производит эскалацию блокировок (Lock Escalation): вместо блокировки отдельных строк блокируется вся физическая таблица целиком. В этот момент работа пользователей полностью парализуется.

Решение №1: Настройка параметра «Количество элементов в транзакции»

Проанализируем стандартные настройки РИБ. По умолчанию в стандартных механизмах обмена параметр КоличествоЭлементовВТранзакции (размер порции данных) часто равен 0. Значение «0» означает, что абсолютно все измененные объекты будут прочитаны, записаны и зарегистрированы в рамках одной-единственной транзакции.

Если у вас за 20 минут регистрируется 10 000 документов продаж со всеми движениями, СУБД попытается обработать их одним пакетом. Это гарантированно вызовет эскалацию блокировок и дедлоки.

Давайте применим метафору: представьте, что вам нужно перевезти бочку воды объемом 1000 литров. Если вы покатите ее целиком, вы потратите много сил, перекроете всю дорогу и заставите всех ждать. Но если вы будете быстро переносить воду ведрами по 10 литров, вы выполните работу порциями, практически не мешая окружающим. Мораль: чем меньше порция (транзакция), тем быстрее она выполняется и тем меньше времени таблица находится в монопольном захвате.

Для решения проблемы нам необходимо принудительно ограничить размер транзакции. Вместо значения 0 установите лимит в 100, 500 или максимум 1000 элементов. Это позволит выполнять запись и очистку регистрации изменений небольшими быстрыми порциями.

Разберем, как сделать это программно. В типовых конфигурациях (например, на базе БСП) для этого можно использовать глобальный поиск по конфигурации строки КоличествоЭлементовВТранзакции. Обычно этот параметр задается в настройках узла обмена или непосредственно в коде обработки РИБ (например, в обработке КонвертацияОбъектовРаспределенныхИнформационныхБаз).

Пример программной настройки параметров чтения сообщения обмена:


ПараметрыЧтения = Новый Структура;
ПараметрыЧтения.Вставить("КоличествоЭлементовВТранзакции", 500); // Оптимальный размер порции

ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
ЧтениеСообщения.НачатьЧтение(ЧтениеXML, ВыбранныйУзел);

// Применение параметров в цикле обработки объектов
Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
    // Платформа будет автоматически фиксировать транзакцию каждые 500 объектов
    ПрочитатьДанные(ЧтениеXML, ПараметрыЧтения);
КонецЦикла;

Решение №2: Программный обход блокировок с использованием прямых запросов

Если стандартный вызов метода ПланыОбмена.ВыбратьИзменения() продолжает блокировать таблицы, мы можем полностью отказаться от него в пользу кастомного чтения изменений. Этот подход позволяет читать измененные данные обычным запросом, который не накладывает жестких эксклюзивных блокировок СУБД.

Рассмотрим этот алгоритм по шагам:

  1. Вместо вызова ПланыОбмена.ВыбратьИзменения() мы выполняем обычный запрос 1С к виртуальной таблице изменений интересующего нас объекта (например, к изменениям справочника или документа).
  2. Запрос быстро выбирает только ссылки измененных объектов. На чтение накладываются минимальные разделяемые блокировки (S), которые не мешают пользователям записывать новые данные.
  3. Мы обрабатываем выбранные объекты порциями, упаковываем их в сообщение обмена.
  4. После успешной отправки или получения подтверждения мы удаляем регистрацию изменений пакетами с помощью метода ПланыОбмена.УдалитьРегистрациюИзменений().

Посмотрим на пример кода, реализующий этот подход:


// Выбираем измененные ссылки документов "РеализацияТоваровУслуг" для узла обмена
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗЛИЧНЫЕ
    |	РеализацияИзменения.Ссылка КАК Ссылка
    |ИЗ
    |	Документ.РеализацияТоваровУслуг.Изменения КАК РеализацияИзменения
    |ГДЕ
    |	РеализацияИзменения.Узел = &Узел";

Запрос.УстановитьПараметр("Узел", ПараметрыСеанса.ТекущийУзелОбмена);
РезультатЗапроса = Запрос.Выполнить();

Если Не РезультатЗапроса.Пустой() Тогда
    Выборка = РезультатЗапроса.Выбрать();
    МассивДляУдаленияРегистрации = Новый Массив;
    
    Пока Выборка.Следующий() Цикл
        // Обрабатываем объект (например, выгружаем в XML)
        ВыгрузитьОбъектВПоток(Выборка.Ссылка);
        
        МассивДляУдаленияРегистрации.Добавить(Выборка.Ссылка);
        
        // Очищаем регистрацию небольшими порциями по 100 элементов
        Если МассивДляУдаленияРегистрации.Количество() >= 100 Тогда
            ПланыОбмена.УдалитьРегистрациюИзменений(ПараметрыСеанса.ТекущийУзелОбмена, МассивДляУдаленияРегистрации);
            МассивДляУдаленияРегистрации.Очистить();
        КонецЕсли;
    КонецЦикла;
    
    // Удаляем остаток
    Если МассивДляУдаленияРегистрации.Количество() > 0 Тогда
        ПланыОбмена.УдалитьРегистрациюИзменений(ПараметрыСеанса.ТекущийУзелОбмена, МассивДляУдаленияРегистрации);
    КонецЕсли;
КонецЕсли;

Этот способ позволяет избежать тяжелой транзакции на изменение номеров сообщений во всей таблице _ChangeRec, распределяя нагрузку во времени.

Решение №3: Тонкая настройка СУБД (MS SQL Server / PostgreSQL)

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

Решение №4: Альтернативные архитектурные паттерны

Если объем передаваемой информации критически огромен (например, сотни тысяч транзакций по продажам в час) и никакие оптимизации транзакций не помогают избавиться от ожидания пользователей, целесообразно рассмотреть переход от стандартных планов обмена РИБ к альтернативным интеграционным решениям:

  1. Паттерн Outbox и шины данных (RabbitMQ, Kafka, 1С:Шина): При записи документа в основной транзакции создается сверхлегкая запись в специальном регистре сведений ("исходящие события"), которая содержит только тип и UUID измененного объекта. Отдельное регламентное фоновое задание асинхронно считывает эти записи, отправляет их в брокер сообщений (RabbitMQ) и сразу удаляет из регистра. Нагрузка на базу снижается в десятки раз по сравнению со стандартным РИБ.
  2. Транзакционная репликация на уровне СУБД (например, Softpoint DBReplication): В самых критических случаях полностью отказываются от РИБ-обмена средствами платформы 1С. Специализированный софт анализирует транзакционные логи MS SQL или PostgreSQL на лету и переносит новые записи в периферийные базы с задержкой менее одной секунды. При этом в самой 1С не запускаются никакие ресурсоемкие сеансы обмена, а пользователи не замечают никаких блокировок.

Подводя итог, отметим, что самым быстрым и эффективным первым шагом для решения проблемы блокировок в РИБ является уход от транзакций нулевого размера. Ограничение количества элементов в транзакции до разумных пределов (от 100 до 500) позволит вернуть комфортную скорость работы пользователям без перестройки всей архитектуры системы.

← На главную