При разработке правил обмена в конфигурации «1С:Конвертация данных 2.1» (КД 2.1) специалисты часто сталкиваются со скрытыми проблемами производительности. Типичный сценарий: выгрузка связанных справочников, таких как Контрагенты, БанковскиеСчета и Банки. В процессе работы может выясниться, что один и тот же объект (например, банк) выгружается или ищется в базе-приемнике несколько раз. Это существенно увеличивает время выполнения обмена и нагрузку на СУБД.
В рамках этой статьи мы детально разберем причины возникновения двойной выгрузки/поиска связанных объектов, найдем способы устранения этой избыточности и рассмотрим архитектурно правильные подходы к решению сложной задачи — установке ссылки на основной расчетный счет контрагента в базе-приемнике без лишних перезаписей и блокировок СУБД.
Рассмотрим стандартное поведение платформы при выгрузке данных. Допустим, у нас есть расчетный счет, у которого заполнен реквизит Банк. По умолчанию в ПКС (Правилах конвертации свойств) для этого реквизита указано ПКО (Правило конвертации объектов) Банки. Если не предпринять дополнительных мер, то при выгрузке каждого расчетного счета система будет инициировать полную выгрузку связанного с ним банка.
Это приводит к следующей последовательности действий:
Банк и начинает выгрузку этого банка по правилу Банки.Если один и тот же банк используется во множестве расчетных счетов, то в XML-файле обмена сам объект банка запишется один раз (благодаря внутренним механизмам КД 2.1, предотвращающим дублирование, если не установлена опция «Не запоминать выгруженные объекты»). Однако поисковые алгоритмы в обработчиках ПКО Банки (особенно если там написан сложный поиск по БИК и наименованию с запросами к базе данных) все равно могут вызываться повторно при разборе узлов свойств.
Для того чтобы избежать полной выгрузки связанных ссылочных объектов и свести процедуру поиска к минимуму, проанализируем и применим специальный флаг.
Для оптимизации этого процесса нам необходимо зайти в настройки ПКС (например, в реквизит Банк внутри ПКО БанковскиеСчета) и установить флаг «Не выгружать объекты свойств источника по ссылкам».
Выясним, как изменяется логика работы системы при включении этой настройки:
Банк целиком по его ПКО.Банки.Это простейшее действие позволяет сократить объем XML-файла и избавляет от лишних транзакций на запись связанных справочников.
Если алгоритм поиска банка очень сложен (например, включает в себя обращение к сторонним регистрам сведений, разбор строк или сложные запросы к СУБД) и выполняется слишком часто, даже простое сопоставление ссылок может замедлить загрузку. В этом случае мы рекомендуем организовать кэширование результатов поиска прямо в оперативной памяти в процессе загрузки.
Разберем по шагам, как это реализовать с помощью встроенного языка «1С»:
Шаг 1. В глобальном обработчике конвертации «Перед загрузкой данных» инициализируем структуру нашего кэша:
Параметры.Вставить("КэшБанков", Новый Соответствие);
Шаг 2. В ПКО Банки в обработчике «Поля поиска» пишем алгоритм, проверяющий наличие банка в кэше перед тем, как выполнять запросы к базе данных:
Кэш = Параметры.КэшБанков;
БИКБанка = СвойстваПоиска.Получить("БИК");
// Формируем уникальный ключ поиска
Если ЗначениеЗаполнено(БИКБанка) Тогда
НайденнаяСсылка = Кэш.Получить(БИКБанка);
Если НайденнаяСсылка <> Неопределено Тогда
СсылкаНаОбъект = НайденнаяСсылка;
ПрекратитьПоиск = Истина;
КонецЕсли;
КонецЕсли;
Шаг 3. В ПКО Банки в обработчике «После загрузки» (или непосредственно в «Полях поиска» при успешном обнаружении объекта в СУБД) сохраняем ссылку в кэш для последующих вызовов:
Если ЗначениеЗаполнено(СсылкаНаОбъект) И ЗначениеЗаполнено(БИКБанка) Тогда
Параметры.КэшБанков.Вставить(БИКБанка, СсылкаНаОбъект);
EndЕсли;
Благодаря этому нехитрому приему повторный поиск одного и того же банка вообще не будет затрагивать базу данных приемника, отрабатывая мгновенно из оперативной памяти.
Перейдем ко второй проблеме, поднятой на форуме. Часто в источнике реквизит контрагента ОсновнойРасчетныйСчет не заполнен, но сами расчетные счета у контрагента в базе есть. При выгрузке нам нужно определить один из счетов и сделать его основным в базе-приемнике.
Рассмотрим наивное и неоптимальное решение, которое часто пытаются применить начинающие разработчики. Они пишут в обработчике «После загрузки» расчетного счета код следующего вида:
// НЕОПТИМАЛЬНЫЙ ВАРИАНТ!
КонтрагентОбъект = Объект.Владелец.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
КонтрагентОбъект.ОсновнойРасчетныйСчет = Объект.Ссылка;
КонтрагентОбъект.Записать();
КонецЕсли;
Проанализируем, почему такой подход является плохим и даже опасным для крупных баз данных:
Записать(). Это порождает колоссальную паразитную нагрузку.Рассмотрим два профессиональных и безопасных способа реализации этой задачи.
Самый красивый и производительный метод заключается в том, чтобы передать правильный основной расчетный счет непосредственно при выгрузке самого контрагента. Платформа КД 2.1 спроектирована так, что она легко справляется со ссылочной целостностью: даже если расчетный счет еще физически не создан в приемнике, его GUID будет записан в реквизит контрагента, а когда счет загрузится следом, «битая» ссылка автоматически восстановится.
Посмотрим на алгоритм реализации:
Контрагенты находим ПКС реквизита ОсновнойРасчетныйСчет.
Если Не ЗначениеЗаполнено(Источник.ОсновнойРасчетныйСчет) Тогда
// Ищем первый доступный расчетный счет контрагента запросом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| БанковскиеСчета.Ссылка КАК Ссылка
|ИЗ
| Справочник.БанковскиеСчета КАК БанковскиеСчета
|ГДЕ
| БанковскиеСчета.Владелец = &Владелец
| И НЕ БанковскиеСчета.ПометкаУдаления";
Запрос.УстановитьПараметр("Владелец", Источник.Ссылка);
Выборка = Запрос.Perform().Выбрать();
Если Выборка.Следующий() Тогда
// Передаем найденный счет в предопределенную переменную Значение
Значение = Выборка.Ссылка;
Иначе
// Если счетов вообще нет, отменяем выгрузку этого свойства
Отказ = Истина;
КонецЕсли;
КонецЕсли;
При таком сценарии контрагент выгружается сразу с заполненным значением основного счета. Нам не требуется писать ни одной строчки кода на стороне приемника, и база данных не совершает ни одной лишней транзакции записи.
Если бизнес-логика построена так, что решение о назначении счета основным принимается только в момент загрузки расчетных счетов, мы можем применить паттерн отложенного обновления.
Разберем, как реализовать отложенную запись:
Шаг 1. В ПКО БанковскиеСчета добавим параметр ЭтоОсновнойСчет (на вкладке «Параметры» правила). Передаем в него признак (например, если счет соответствует определенному условию или является единственным выгружаемым).
Шаг 2. В глобальном обработчике конвертации «Перед загрузкой данных» создаем таблицу или соответствие для накопления задач обновления контрагентов:
Параметры.Вставить("ОчередьОбновленияКонтрагентов", Новый Соответствие);
Шаг 3. В ПКО БанковскиеСчета в обработчике «После загрузки» (который выполняется перед непосредственной записью счета в БД) фиксируем необходимость сделать данный счет основным у его владельца:
Если ПараметрыОбъекта <> Неопределено Тогда
Если ПараметрыОбъекта.Получить("ЭтоОсновнойСчет") = Истина Тогда
// Запоминаем связь в глобальных параметрах без обращения к БД
Параметры.ОчередьОбновленияКонтрагентов.Вставить(Объект.Владелец, Объект.Ссылка);
КонецЕсли;
КонецЕсли;
Шаг 4. В глобальном обработчике конвертации «После загрузки данных» (когда все объекты уже гарантированно записаны в информационную базу приемника) выполняем обновление контрагентов единым пакетом:
Очередь = Параметры.ОчередьОбновленияКонтрагентов;
Для Каждого ЗаписьИзОчереди Из Очередь Цикл
КонтрагентСсылка = ЗаписьИзОчереди.Ключ;
СчетСсылка = ЗаписьИзОчереди.Значение;
// Получаем объект один раз в самом конце загрузки
КонтрагентОбъект = КонтрагентСсылка.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
// Проверяем, действительно ли значение изменилось, чтобы избежать лишней записи
Если КонтрагентОбъект.ОсновнойРасчетныйСчет <> СчетСсылка Тогда
КонтрагентОбъект.ОсновнойРасчетныйСчет = СчетСсылка;
КонтрагентОбъект.Записать();
КонецЕсли;
КонецЕсли;
КонецЦикла;
Этот способ является наиболее масштабируемым и безопасным при сложной загрузке больших объемов данных. Он исключает блокировки СУБД, гарантирует отсутствие «битых» ссылок и сводит количество дисковых операций записи контрагентов к строгому минимуму (не более одного раза на каждого измененного контрагента).