Почему объекты выгружаются дважды в КД 2.1 и как оптимально связать контрагента с его основным расчетным счетом?

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

При разработке правил обмена в конфигурации «1С:Конвертация данных 2.1» (КД 2.1) специалисты часто сталкиваются со скрытыми проблемами производительности. Типичный сценарий: выгрузка связанных справочников, таких как Контрагенты, БанковскиеСчета и Банки. В процессе работы может выясниться, что один и тот же объект (например, банк) выгружается или ищется в базе-приемнике несколько раз. Это существенно увеличивает время выполнения обмена и нагрузку на СУБД.

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

Часть 1. Разбор проблемы избыточной выгрузки и поиска связанных объектов

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

Это приводит к следующей последовательности действий:

  1. Выгружается расчетный счет по своему ПКО.
  2. Платформа видит заполненное свойство Банк и начинает выгрузку этого банка по правилу Банки.
  3. При загрузке в базу-приемник сначала создается/ищется банк, а затем создается/ищется расчетный счет, в реквизит которого записывается ссылка на найденный банк.

Если один и тот же банк используется во множестве расчетных счетов, то в XML-файле обмена сам объект банка запишется один раз (благодаря внутренним механизмам КД 2.1, предотвращающим дублирование, если не установлена опция «Не запоминать выгруженные объекты»). Однако поисковые алгоритмы в обработчиках ПКО Банки (особенно если там написан сложный поиск по БИК и наименованию с запросами к базе данных) все равно могут вызываться повторно при разборе узлов свойств.

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

Решение: Использование флага «Не выгружать объекты свойств источника по ссылкам»

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

Выясним, как изменяется логика работы системы при включении этой настройки:

Это простейшее действие позволяет сократить объем XML-файла и избавляет от лишних транзакций на запись связанных справочников.

Альтернативное решение: Организация поискового кэша в оперативной памяти

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

Разберем по шагам, как это реализовать с помощью встроенного языка «1С»:

Шаг 1. В глобальном обработчике конвертации «Перед загрузкой данных» инициализируем структуру нашего кэша:

Параметры.Вставить("КэшБанков", Новый Соответствие);

Шаг 2. В ПКО Банки в обработчике «Поля поиска» пишем алгоритм, проверяющий наличие банка в кэше перед тем, как выполнять запросы к базе данных:


Кэш = Параметры.КэшБанков;
БИКБанка = СвойстваПоиска.Получить("БИК");

// Формируем уникальный ключ поиска
Если ЗначениеЗаполнено(БИКБанка) Тогда
    НайденнаяСсылка = Кэш.Получить(БИКБанка);
    
    Если НайденнаяСсылка <> Неопределено Тогда
        СсылкаНаОбъект = НайденнаяСсылка;
        ПрекратитьПоиск = Истина;
    КонецЕсли;
КонецЕсли;

Шаг 3. В ПКО Банки в обработчике «После загрузки» (или непосредственно в «Полях поиска» при успешном обнаружении объекта в СУБД) сохраняем ссылку в кэш для последующих вызовов:


Если ЗначениеЗаполнено(СсылкаНаОбъект) И ЗначениеЗаполнено(БИКБанка) Тогда
    Параметры.КэшБанков.Вставить(БИКБанка, СсылкаНаОбъект);
EndЕсли;

Благодаря этому нехитрому приему повторный поиск одного и того же банка вообще не будет затрагивать базу данных приемника, отрабатывая мгновенно из оперативной памяти.

Часть 2. Оптимальная установка связи «Контрагент — Основной расчетный счет»

Перейдем ко второй проблеме, поднятой на форуме. Часто в источнике реквизит контрагента ОсновнойРасчетныйСчет не заполнен, но сами расчетные счета у контрагента в базе есть. При выгрузке нам нужно определить один из счетов и сделать его основным в базе-приемнике.

Рассмотрим наивное и неоптимальное решение, которое часто пытаются применить начинающие разработчики. Они пишут в обработчике «После загрузки» расчетного счета код следующего вида:


// НЕОПТИМАЛЬНЫЙ ВАРИАНТ!
КонтрагентОбъект = Объект.Владелец.ПолучитьОбъект();
Если КонтрагентОбъект <> Неопределено Тогда
    КонтрагентОбъект.ОсновнойРасчетныйСчет = Объект.Ссылка;
    КонтрагентОбъект.Записать();
КонецЕсли;

Проанализируем, почему такой подход является плохим и даже опасным для крупных баз данных:

Рассмотрим два профессиональных и безопасных способа реализации этой задачи.

Способ А. Вычисление основного счета на этапе выгрузки контрагента (Рекомендуемый)

Самый красивый и производительный метод заключается в том, чтобы передать правильный основной расчетный счет непосредственно при выгрузке самого контрагента. Платформа КД 2.1 спроектирована так, что она легко справляется со ссылочной целостностью: даже если расчетный счет еще физически не создан в приемнике, его GUID будет записан в реквизит контрагента, а когда счет загрузится следом, «битая» ссылка автоматически восстановится.

Посмотрим на алгоритм реализации:

  1. В ПКО Контрагенты находим ПКС реквизита ОсновнойРасчетныйСчет.
  2. В обработчике ПКС «Перед выгрузкой» пишем код, который определяет расчетный счет, если в источнике он пуст:

Если Не ЗначениеЗаполнено(Источник.ОсновнойРасчетныйСчет) Тогда
    // Ищем первый доступный расчетный счет контрагента запросом
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |   БанковскиеСчета.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.БанковскиеСчета КАК БанковскиеСчета
        |ГДЕ
        |   БанковскиеСчета.Владелец = &Владелец
        |   И НЕ БанковскиеСчета.ПометкаУдаления";
        
    Запрос.УстановитьПараметр("Владелец", Источник.Ссылка);
    Выборка = Запрос.Perform().Выбрать();
    
    Если Выборка.Следующий() Тогда
        // Передаем найденный счет в предопределенную переменную Значение
        Значение = Выборка.Ссылка;
    Иначе
        // Если счетов вообще нет, отменяем выгрузку этого свойства
        Отказ = Истина;
    КонецЕсли;
КонецЕсли;

При таком сценарии контрагент выгружается сразу с заполненным значением основного счета. Нам не требуется писать ни одной строчки кода на стороне приемника, и база данных не совершает ни одной лишней транзакции записи.

Способ Б. Использование параметров объекта и отложенной очереди обновления

Если бизнес-логика построена так, что решение о назначении счета основным принимается только в момент загрузки расчетных счетов, мы можем применить паттерн отложенного обновления.

Разберем, как реализовать отложенную запись:

Шаг 1. В ПКО БанковскиеСчета добавим параметр ЭтоОсновнойСчет (на вкладке «Параметры» правила). Передаем в него признак (например, если счет соответствует определенному условию или является единственным выгружаемым).

Шаг 2. В глобальном обработчике конвертации «Перед загрузкой данных» создаем таблицу или соответствие для накопления задач обновления контрагентов:

Параметры.Вставить("ОчередьОбновленияКонтрагентов", Новый Соответствие);

Шаг 3. В ПКО БанковскиеСчета в обработчике «После загрузки» (который выполняется перед непосредственной записью счета в БД) фиксируем необходимость сделать данный счет основным у его владельца:


Если ПараметрыОбъекта <> Неопределено Тогда
    Если ПараметрыОбъекта.Получить("ЭтоОсновнойСчет") = Истина Тогда
        // Запоминаем связь в глобальных параметрах без обращения к БД
        Параметры.ОчередьОбновленияКонтрагентов.Вставить(Объект.Владелец, Объект.Ссылка);
    КонецЕсли;
КонецЕсли;

Шаг 4. В глобальном обработчике конвертации «После загрузки данных» (когда все объекты уже гарантированно записаны в информационную базу приемника) выполняем обновление контрагентов единым пакетом:


Очередь = Параметры.ОчередьОбновленияКонтрагентов;

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

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

← На главную