При работе с распределенными информационными базами (РИБ) разработчики и системные администраторы часто сталкиваются со следующей проблемой: во время выполнения сеанса загрузки и выгрузки данных на таблицы базы данных накладываются жесткие автоматические и транзакционные блокировки. В результате пользователи в периферийных или центральной базах вынуждены простаивать («курить») до тех пор, пока процесс обмена полностью не завершится. Иногда это занимает 10 и более минут, что абсолютно недопустимо для систем, работающих в режиме 24/7.
В этой статье мы подробно проанализируем, почему возникают такие блокировки на уровне СУБД, разберем физику этого процесса и рассмотрим эффективные методы борьбы с зависанием базы данных во время РИБ-обмена.
Давайте выясним физическую природу проблемы. Когда запускается стандартный процесс обмена, платформа 1С выполняет метод ПланыОбмена.ВыбратьИзменения(). В этот момент на уровне СУБД (MS SQL Server или PostgreSQL) происходят следующие процессы:
_ChangeRec).UPDATE к таблицам изменений.UPDATE СУБД накладывает эксклюзивные (X) блокировки на строки таблицы изменений.В это же время обычные пользователи продолжают активно работать: проводить документы, записывать справочники. Их транзакции пишут данные в основные таблицы (накладывая X-блокировки) и пытаются зарегистрировать эти изменения в таблицах _ChangeRec. Возникает классический Deadlock (взаимоблокировка): сеанс обмена удерживает таблицу изменений и ожидает доступ к основной таблице, а сеанс пользователя удерживает основную таблицу и ждет освобождения таблицы изменений.
Если объем передаваемых данных велик, СУБД автоматически производит эскалацию блокировок (Lock Escalation): вместо блокировки отдельных строк блокируется вся физическая таблица целиком. В этот момент работа пользователей полностью парализуется.
Проанализируем стандартные настройки РИБ. По умолчанию в стандартных механизмах обмена параметр КоличествоЭлементовВТранзакции (размер порции данных) часто равен 0. Значение «0» означает, что абсолютно все измененные объекты будут прочитаны, записаны и зарегистрированы в рамках одной-единственной транзакции.
Если у вас за 20 минут регистрируется 10 000 документов продаж со всеми движениями, СУБД попытается обработать их одним пакетом. Это гарантированно вызовет эскалацию блокировок и дедлоки.
Давайте применим метафору: представьте, что вам нужно перевезти бочку воды объемом 1000 литров. Если вы покатите ее целиком, вы потратите много сил, перекроете всю дорогу и заставите всех ждать. Но если вы будете быстро переносить воду ведрами по 10 литров, вы выполните работу порциями, практически не мешая окружающим. Мораль: чем меньше порция (транзакция), тем быстрее она выполняется и тем меньше времени таблица находится в монопольном захвате.
Для решения проблемы нам необходимо принудительно ограничить размер транзакции. Вместо значения 0 установите лимит в 100, 500 или максимум 1000 элементов. Это позволит выполнять запись и очистку регистрации изменений небольшими быстрыми порциями.
Разберем, как сделать это программно. В типовых конфигурациях (например, на базе БСП) для этого можно использовать глобальный поиск по конфигурации строки КоличествоЭлементовВТранзакции. Обычно этот параметр задается в настройках узла обмена или непосредственно в коде обработки РИБ (например, в обработке КонвертацияОбъектовРаспределенныхИнформационныхБаз).
Пример программной настройки параметров чтения сообщения обмена:
ПараметрыЧтения = Новый Структура;
ПараметрыЧтения.Вставить("КоличествоЭлементовВТранзакции", 500); // Оптимальный размер порции
ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
ЧтениеСообщения.НачатьЧтение(ЧтениеXML, ВыбранныйУзел);
// Применение параметров в цикле обработки объектов
Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
// Платформа будет автоматически фиксировать транзакцию каждые 500 объектов
ПрочитатьДанные(ЧтениеXML, ПараметрыЧтения);
КонецЦикла;
Если стандартный вызов метода ПланыОбмена.ВыбратьИзменения() продолжает блокировать таблицы, мы можем полностью отказаться от него в пользу кастомного чтения изменений. Этот подход позволяет читать измененные данные обычным запросом, который не накладывает жестких эксклюзивных блокировок СУБД.
Рассмотрим этот алгоритм по шагам:
ПланыОбмена.ВыбратьИзменения() мы выполняем обычный запрос 1С к виртуальной таблице изменений интересующего нас объекта (например, к изменениям справочника или документа).ПланыОбмена.УдалитьРегистрациюИзменений().Посмотрим на пример кода, реализующий этот подход:
// Выбираем измененные ссылки документов "РеализацияТоваровУслуг" для узла обмена
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| РеализацияИзменения.Ссылка КАК Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг.Изменения КАК РеализацияИзменения
|ГДЕ
| РеализацияИзменения.Узел = &Узел";
Запрос.УстановитьПараметр("Узел", ПараметрыСеанса.ТекущийУзелОбмена);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
МассивДляУдаленияРегистрации = Новый Массив;
Пока Выборка.Следующий() Цикл
// Обрабатываем объект (например, выгружаем в XML)
ВыгрузитьОбъектВПоток(Выборка.Ссылка);
МассивДляУдаленияРегистрации.Добавить(Выборка.Ссылка);
// Очищаем регистрацию небольшими порциями по 100 элементов
Если МассивДляУдаленияРегистрации.Количество() >= 100 Тогда
ПланыОбмена.УдалитьРегистрациюИзменений(ПараметрыСеанса.ТекущийУзелОбмена, МассивДляУдаленияРегистрации);
МассивДляУдаленияРегистрации.Очистить();
КонецЕсли;
КонецЦикла;
// Удаляем остаток
Если МассивДляУдаленияРегистрации.Количество() > 0 Тогда
ПланыОбмена.УдалитьРегистрациюИзменений(ПараметрыСеанса.ТекущийУзелОбмена, МассивДляУдаленияРегистрации);
КонецЕсли;
КонецЕсли;
Этот способ позволяет избежать тяжелой транзакции на изменение номеров сообщений во всей таблице _ChangeRec, распределяя нагрузку во времени.
Часто проблема кроется в неоптимальном поведении самой СУБД при работе с таблицами регистрации изменений. Поскольку данные в таблицы _ChangeRec постоянно записываются и стираются миллионами строк, эти таблицы сильно фрагментируются. Проанализируем настройки СУБД, которые помогут улучшить ситуацию:
Для этого в MS SQL выполняется команда:
ALTER TABLE ИмяТаблицыРегистрацииИзменений SET (LOCK_ESCALATION = DISABLE);
tempdb. Это значительно повышает общую производительность обменов.Если объем передаваемой информации критически огромен (например, сотни тысяч транзакций по продажам в час) и никакие оптимизации транзакций не помогают избавиться от ожидания пользователей, целесообразно рассмотреть переход от стандартных планов обмена РИБ к альтернативным интеграционным решениям:
Подводя итог, отметим, что самым быстрым и эффективным первым шагом для решения проблемы блокировок в РИБ является уход от транзакций нулевого размера. Ограничение количества элементов в транзакции до разумных пределов (от 100 до 500) позволит вернуть комфортную скорость работы пользователям без перестройки всей архитектуры системы.