Как оптимизировать поиск контрагентов по ИНН и ускорить загрузку платежей в 1С 7.7

Программист 1С v7.7 1С:Комплексная автоматизация Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При пакетной обработке данных, например при импорте банковских выписок или платежных поручений в конфигурации 1С:Предприятие 7.7 (в частности, в «Комплексной конфигурации»), разработчики часто сталкиваются с критическим падением производительности. Время выполнения процедуры может достигать от 30 минут до часа и более. Выясним причину такого замедления и разберем по шагам эффективные методы оптимизации кода.

Анализ узких мест в исходном коде

Проанализируем типовой фрагмент кода, используемый для поиска собственной организации или контрагента по ИНН и последующего заполнения бухгалтерской аналитики:


Вид = "ЮрЛица"; 
ДлинаИНН = 10;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "//{{ЗАПРОС(НайтиСвоеЮрЛицо)
|ТекущийЭлемент = Справочник." + Вид + ".ТекущийЭлемент;
|ИНН = Справочник." + Вид + ".ИНН;
|Группировка ТекущийЭлемент без групп;
|Условие((Лев(ИНН,  " + ДлинаИНН + ") = ИННКраткий) ИЛИ (Лев(ИНН, 12) = ИННКраткий));
|"//}}ЗАПРОС
;

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

В приведенном алгоритме присутствует сразу несколько критических архитектурных проблем, вызывающих зависание программы:

  1. Неиндексируемый строковый поиск в объекте Запрос. Использование функции Лев() внутри секции Условие приводит к полному последовательному сканированию таблицы справочника. Если такой запрос запускается для каждой строки выписки, база данных выполняет сотни тысяч холостых чтений.
  2. Многократное создание объектов внутри цикла. Команды СоздатьОбъект("Справочник.Контрагенты") и СоздатьОбъект("БухгалтерскиеИтоги") создают внутренние COM-объекты платформы при каждой итерации, что сильно нагружает оперативную память и процессор.
  3. Циклический расчет бухгалтерских итогов. Метод Ит.ВыполнитьЗапрос(...) на дату документа производит сложное чтение таблиц итогов и проводок. Вызов этого метода для каждого найденного платежа блокирует выполнение обработки на длительное время.
  4. Поиск по неиндексированным реквизитам. Вызов НайтиПоРеквизиту("ЮрФизЛицо", Объект, 1) работает быстро только при включенном свойстве «Сортировка» в конфигураторе. В противном случае выполняется полный перебор справочника.

Способ 1. Кэширование справочников в память до начала цикла

Рассмотрим самое эффективное решение для поиска контрагентов и собственных юрлиц: предварительная загрузка данных в оперативную память перед стартом обработки файла.

Создадим объект СписокЗначений или ТаблицаЗначений до начала цикла загрузки платежей. В строковое представление списка запишем очищенный ИНН, а в значение — ссылку на элемент справочника:


Перем спрСвоиЮрЛица;

// 1. Инициализируем и заполняем кэш до начала цикла загрузки
спрСвоиЮрЛица = СоздатьОбъект("СписокЗначений");

Спр = СоздатьОбъект("Справочник.ЮрЛица");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
    Если Спр.ЭтоГруппа() = 0 Тогда
        ОчищенныйИНН = СокрЛП(Спр.ИНН);
        // Заполняем список: Значение - ссылка, Представление - ИНН
        Если ПустаяСтрока(ОчищенныйИНН) = 0 Тогда
            спрСвоиЮрЛица.ДобавитьЗначение(Спр.ТекущийЭлемент(), ОчищенныйИНН);
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Теперь в основном цикле обработки строк банковской выписки поиск выполняется мгновенно с помощью методов Получить() или НайтиЗначение():


// 2. Быстрый поиск в памяти внутри цикла обработки выписки
Поз = спрСвоиЮрЛица.НайтиЗначение(ИННКраткий);
Если Поз > 0 Тогда
    НайденноеЮрЛицо = спрСвоиЮрЛица.ПолучитьЗначение(Поз, СтрИНН);
Иначе
    // Если в процессе загрузки создается новый элемент, сразу добавляем его в кэш
    // спрСвоиЮрЛица.ДобавитьЗначение(НовыйЭлемент, НовыйИНН);
КонецЕсли;

Способ 2. Вынос объектов и оптимизация бухгалтерских итогов

Разберем, как правильно организовать работу с объектом БухгалтерскиеИтоги. Объекты создаются один раз до начала циклов, а итоги рассчитываются пакетом:

  1. Выносим инициализацию объектов СоздатьОбъект("Справочник.Контрагенты") и СоздатьОбъект("БухгалтерскиеИтоги") за пределы цикла загрузки строк.
  2. Если требуется получить остатки по счету расчетов (например, 62.1 или 60.1), выполняем один общий расчет итогов на дату документа по всему счету без фильтрации по конкретному контрагенту.
  3. Внутри цикла используем быстрое позиционирование методом Ит.ПолучитьСубконто(1, , НайденныйКонтрагент).

Посмотрим на пример правильной структуры кода:


// Инициализация вне цикла
КПлательщик = СоздатьОбъект("Справочник.Контрагенты");
Ит = СоздатьОбъект("БухгалтерскиеИтоги");
Ит.ИспользоватьПланСчетов(ОсновнойПланСчетов());
Ит.ИспользоватьСубконто(ВидыСубконто.Контрагенты);
Ит.ИспользоватьСубконто(ВидыСубконто.Договоры);
Ит.ВключатьСубсчета(1);

// Выполняем один общий запрос на дату документа
Ит.ВыполнитьЗапрос(, ДатаДок, "62.1",,, 1,, "С");

// В цикле документов только считываем уже рассчитанные остатки:
Если Ит.ПолучитьСубконто(ВидыСубконто.Контрагенты, 1, КПлательщик.ТекущийЭлемент()) = 1 Тогда
    Ит.ВыбратьСубконто(ВидыСубконто.Договоры);
    Пока Ит.ПолучитьСубконто(ВидыСубконто.Договоры) = 1 Цикл
        Если Ит.СКД() > 0 Тогда
            // Найдено сальдо по договору
            ДоговорДолга = Ит.Субконто(ВидыСубконто.Договоры);
            Прервать;
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Способ 3. Ускорение записи через механизм транзакций

При записи и проведении большого количества документов платформа 1С 7.7 для каждого объекта выполняет дисковые операции и пересчет индексов. Объединение всей загрузки в единую транзакцию сокращает время записи в разы:


НачатьТранзакцию();

Попытка
    // Основной цикл обработки и создания документов
    Пока Файл.ПрочитатьСтроку() = 1 Цикл
        // Создание, заполнение и запись документа
        Док.Записать();
    КонецЦикла;
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    Сообщить("Ошибка при загрузке: " + ОписаниеОшибки(), "!");
КонецПопытки;

Важно: При очень больших объемах данных (тысячи документов) транзакцию рекомендуется фиксировать порциями (например, каждые 100–200 документов), чтобы не переполнять журнал транзакций СУБД.

Способ 4. Проверка индексов метаданных в конфигураторе

Если в алгоритме сохраняется вызов метода НайтиПоРеквизиту(), убедимся, что реквизиты поиска проиндексированы:

  1. Откроем Конфигуратор.
  2. Перейдем в дерево метаданных к справочнику (например, Контрагенты или ЮрЛица).
  3. Откроем свойства реквизита (ИНН, ЮрФизЛицо).
  4. Установим признак «Сортировка» (или «Отбор» для периодических/числовых значений).
  5. Сохраним конфигурацию. После переиндексации метод НайтиПоРеквизиту() будет выполняться по индексу B-Tree за доли миллисекунды.
← На главную