При пакетной обработке данных, например при импорте банковских выписок или платежных поручений в конфигурации 1С:Предприятие 7.7 (в частности, в «Комплексной конфигурации»), разработчики часто сталкиваются с критическим падением производительности. Время выполнения процедуры может достигать от 30 минут до часа и более. Выясним причину такого замедления и разберем по шагам эффективные методы оптимизации кода.
Проанализируем типовой фрагмент кода, используемый для поиска собственной организации или контрагента по ИНН и последующего заполнения бухгалтерской аналитики:
Вид = "ЮрЛица";
ДлинаИНН = 10;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = "//{{ЗАПРОС(НайтиСвоеЮрЛицо)
|ТекущийЭлемент = Справочник." + Вид + ".ТекущийЭлемент;
|ИНН = Справочник." + Вид + ".ИНН;
|Группировка ТекущийЭлемент без групп;
|Условие((Лев(ИНН, " + ДлинаИНН + ") = ИННКраткий) ИЛИ (Лев(ИНН, 12) = ИННКраткий));
|"//}}ЗАПРОС
;
Если Запрос.Выполнить(ТекстЗапроса) <> 0 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
Объект.НайтиЭлемент(Запрос.ТекущийЭлемент);
Если Объект.Выбран() <> 0 Тогда
КПлательшик = СоздатьОбъект("Справочник.Контрагенты");
КПлательшик.НайтиПоРеквизиту("ЮрФизЛицо", Объект, 1);
Ит = СоздатьОбъект("БухгалтерскиеИтоги");
Ит.ИспользоватьПланСчетов(ОсновнойПланСчетов());
Ит.ИспользоватьСубконто(ВидыСубконто.Контрагенты, КПлательшик, 1);
Ит.ИспользоватьСубконто(ВидыСубконто.Договоры,, 1);
Ит.ВключатьСубсчета(1);
Ит.ВыполнитьЗапрос(, ДатаДок, СчетТекст,,, 1,, "С");
// Обработка результатов...
КонецЕсли;
КонецЦикла;
КонецЕсли;
В приведенном алгоритме присутствует сразу несколько критических архитектурных проблем, вызывающих зависание программы:
Запрос. Использование функции Лев() внутри секции Условие приводит к полному последовательному сканированию таблицы справочника. Если такой запрос запускается для каждой строки выписки, база данных выполняет сотни тысяч холостых чтений.СоздатьОбъект("Справочник.Контрагенты") и СоздатьОбъект("БухгалтерскиеИтоги") создают внутренние COM-объекты платформы при каждой итерации, что сильно нагружает оперативную память и процессор.Ит.ВыполнитьЗапрос(...) на дату документа производит сложное чтение таблиц итогов и проводок. Вызов этого метода для каждого найденного платежа блокирует выполнение обработки на длительное время.НайтиПоРеквизиту("ЮрФизЛицо", Объект, 1) работает быстро только при включенном свойстве «Сортировка» в конфигураторе. В противном случае выполняется полный перебор справочника.Рассмотрим самое эффективное решение для поиска контрагентов и собственных юрлиц: предварительная загрузка данных в оперативную память перед стартом обработки файла.
Создадим объект СписокЗначений или ТаблицаЗначений до начала цикла загрузки платежей. В строковое представление списка запишем очищенный ИНН, а в значение — ссылку на элемент справочника:
Перем спрСвоиЮрЛица;
// 1. Инициализируем и заполняем кэш до начала цикла загрузки
спрСвоиЮрЛица = СоздатьОбъект("СписокЗначений");
Спр = СоздатьОбъект("Справочник.ЮрЛица");
Спр.ВыбратьЭлементы();
Пока Спр.ПолучитьЭлемент() = 1 Цикл
Если Спр.ЭтоГруппа() = 0 Тогда
ОчищенныйИНН = СокрЛП(Спр.ИНН);
// Заполняем список: Значение - ссылка, Представление - ИНН
Если ПустаяСтрока(ОчищенныйИНН) = 0 Тогда
спрСвоиЮрЛица.ДобавитьЗначение(Спр.ТекущийЭлемент(), ОчищенныйИНН);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Теперь в основном цикле обработки строк банковской выписки поиск выполняется мгновенно с помощью методов Получить() или НайтиЗначение():
// 2. Быстрый поиск в памяти внутри цикла обработки выписки
Поз = спрСвоиЮрЛица.НайтиЗначение(ИННКраткий);
Если Поз > 0 Тогда
НайденноеЮрЛицо = спрСвоиЮрЛица.ПолучитьЗначение(Поз, СтрИНН);
Иначе
// Если в процессе загрузки создается новый элемент, сразу добавляем его в кэш
// спрСвоиЮрЛица.ДобавитьЗначение(НовыйЭлемент, НовыйИНН);
КонецЕсли;
Разберем, как правильно организовать работу с объектом БухгалтерскиеИтоги. Объекты создаются один раз до начала циклов, а итоги рассчитываются пакетом:
СоздатьОбъект("Справочник.Контрагенты") и СоздатьОбъект("БухгалтерскиеИтоги") за пределы цикла загрузки строк.Ит.ПолучитьСубконто(1, , НайденныйКонтрагент).Посмотрим на пример правильной структуры кода:
// Инициализация вне цикла
КПлательщик = СоздатьОбъект("Справочник.Контрагенты");
Ит = СоздатьОбъект("БухгалтерскиеИтоги");
Ит.ИспользоватьПланСчетов(ОсновнойПланСчетов());
Ит.ИспользоватьСубконто(ВидыСубконто.Контрагенты);
Ит.ИспользоватьСубконто(ВидыСубконто.Договоры);
Ит.ВключатьСубсчета(1);
// Выполняем один общий запрос на дату документа
Ит.ВыполнитьЗапрос(, ДатаДок, "62.1",,, 1,, "С");
// В цикле документов только считываем уже рассчитанные остатки:
Если Ит.ПолучитьСубконто(ВидыСубконто.Контрагенты, 1, КПлательщик.ТекущийЭлемент()) = 1 Тогда
Ит.ВыбратьСубконто(ВидыСубконто.Договоры);
Пока Ит.ПолучитьСубконто(ВидыСубконто.Договоры) = 1 Цикл
Если Ит.СКД() > 0 Тогда
// Найдено сальдо по договору
ДоговорДолга = Ит.Субконто(ВидыСубконто.Договоры);
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
При записи и проведении большого количества документов платформа 1С 7.7 для каждого объекта выполняет дисковые операции и пересчет индексов. Объединение всей загрузки в единую транзакцию сокращает время записи в разы:
НачатьТранзакцию();
Попытка
// Основной цикл обработки и создания документов
Пока Файл.ПрочитатьСтроку() = 1 Цикл
// Создание, заполнение и запись документа
Док.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Сообщить("Ошибка при загрузке: " + ОписаниеОшибки(), "!");
КонецПопытки;
Важно: При очень больших объемах данных (тысячи документов) транзакцию рекомендуется фиксировать порциями (например, каждые 100–200 документов), чтобы не переполнять журнал транзакций СУБД.
Если в алгоритме сохраняется вызов метода НайтиПоРеквизиту(), убедимся, что реквизиты поиска проиндексированы:
Контрагенты или ЮрЛица).ИНН, ЮрФизЛицо).НайтиПоРеквизиту() будет выполняться по индексу B-Tree за доли миллисекунды.