При разработке и доработке конфигураций на платформе 1С разработчики часто сталкиваются с ситуацией, когда процедура проведения или записи документа начинает сильно замедляться. При анализе замера производительности нередко выясняется, что львиную долю времени занимает, казалось бы, безобидная проверка условия в цикле по строкам табличной части — например, обращение к реквизиту номенклатуры через точку вида СтрокаТЧ.Номенклатура.ВидНоменклатуры. Разберем подробно, почему возникает эта проблема, что происходит внутри платформы и СУБД, а также рассмотрим правильные способы оптимизации данного участка кода.
Выясним причину такого поведения платформы. Когда мы пишем конструкцию СтрокаТЧ.Номенклатура.ВидНоменклатуры, мы обращаемся к реквизиту объекта через ссылку. Платформа 1С устроена так, что при разыменовании ссылки (обращении через точку к реквизиту ссылочного типа) происходит следующее:
ВидНоменклатуры. Вместо этого в память считывается вся строка таблицы справочника (все реквизиты объекта). Если объект содержит множество реквизитов, длинные строковые описания или хранилища значений, все эти данные передаются от СУБД на сервер 1С.Проанализируем типичную ошибочную попытку оптимизации. Часто разработчики пытаются заменить обращение через точку на запрос внутри самого цикла:
Для Каждого СтрокаТЧ Из ДокОбъект.Товары Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СтрокаТЧ.Номенклатура);
// Выполнение запроса в цикле...
КонецЦикла;
Такой подход не решает проблему, а лишь немного снижает объем передаваемых данных, сохраняя главное узкое место — выполнение сотен отдельных запросов к СУБД. Запросы в цикле являются грубым нарушением стандартов разработки 1С.
Рассмотрим наиболее простое, универсальное и эффективное решение. Вместо того чтобы обращаться к базе данных на каждом шаге цикла, мы выполняем один единственный запрос перед началом цикла, передав в него список всех ссылок из табличной части.
Разберем реализацию по шагам:
ВыгрузитьКолонку().ГДЕ Номенклатура.Ссылка В (&СписокНоменклатуры).Соответствие, поиск по которому выполняется за $O(1)$.Посмотрим на пример реализации данного алгоритма:
// 1. Формируем список ссылок номенклатуры из табличной части документа
МассивНоменклатуры = ДокОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
// 2. Выполняем один запрос для получения позиций с нужным видом
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивНоменклатуры)
| И Номенклатура.ВидНоменклатуры = ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.ПодарочныйСертификат)";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
// 3. Заполняем соответствие для мгновенной проверки в памяти
Сертификаты = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сертификаты.Вставить(Выборка.Номенклатура, Истина);
КонецЦикла;
// 4. Основной цикл по табличной части без обращений через точку
Для Каждого СтрокаТЧ Из ДокОбъект.Товары Цикл
Если Сертификаты[СтрокаТЧ.Номенклатура] = Неопределено Тогда
// Номенклатура НЕ является подарочным сертификатом
// Выполняем требуемые проверки или действия
КонецЕсли;
КонецЦикла;
Важное замечание: Обратите внимание на использование объекта Соответствие вместо метода Массив.Найти(). Поиск по массиву выполняет линейный перебор элементов ($O(N)$), что при больших объемах строк вновь приведет к квадратичной сложности $O(N \times M)$. Поиск по ключу в Соответствие работает практически мгновенно благодаря хэшированию.
Если в документе содержится большое количество строк (более 1 000 – 2 000 позиций), передача огромного массива ссылок в параметр В (&СписокНоменклатуры) может перегрузить парсер SQL-запросов и привести к неоптимальному плану выполнения в СУБД. В таких случаях по стандартам разработки 1С рекомендуется использовать Временные Таблицы.
Разберем по шагам реализацию через временную таблицу:
ТаблицаЗначений.МенеджерВременныхТаблиц.ИНДЕКСИРОВАТЬ ПО.Посмотрим на пример кода:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ТЧ.Номенклатура КАК Номенклатура,
| ТЧ.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТаблицаТовары КАК ТЧ
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.НомерСтроки КАК НомерСтроки,
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ЕСТЬNULL(Номенклатура.ВидНоменклатуры, ЗНАЧЕНИЕ(Перечисление.ВидыНоменклатуры.ПустаяСсылка)) КАК ВидНоменклатуры
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура
| ПО ВТ_Товары.Номенклатура = Номенклатура.Ссылка";
Запрос.УстановитьПараметр("ТаблицаТовары", ДокОбъект.Товары.Выгрузить(, "Номенклатура, НомерСтроки"));
РезультатТаблица = Запрос.Выполнить().Выгрузить();
РезультатТаблица.Индексы.Добавить("Номенклатура");
// Далее выполняем обход или используем полученную таблицу
Для Каждого СтрокаТЧ Из ДокОбъект.Товары Цикл
СтрокаДанных = РезультатТаблица.Найти(СтрокаТЧ.Номенклатура, "Номенклатура");
Если СтрокаДанных <> Неопределено И СтрокаДанных.ВидНоменклатуры <> Перечисления.ВидыНоменклатуры.ПодарочныйСертификат Тогда
// Выполнение логики проверки
КонецЕсли;
КонецЦикла;
Если в вашей конфигурации подключена Библиотека стандартных подсистем (БСП), для исключения обращений через точку и избежания ручного написания запросов следует использовать штатную функцию модуля ОбщегоНазначения:
// Получение значений реквизитов для массива ссылок за один пакетный запрос
МассивНоменклатуры = ДокОбъект.Товары.ВыгрузитьКолонку("Номенклатура");
ДанныеНоменклатуры = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивНоменклатуры, "ВидНоменклатуры");
Для Каждого СтрокаТЧ Из ДокОбъект.Товары Цикл
СтруктураРеквизитов = ДанныеНоменклатуры[СтрокаТЧ.Номенклатура];
Если СтруктураРеквизитов <> Неопределено
И СтруктураРеквизитов.ВидНоменклатуры <> Перечисления.ВидыНоменклатуры.ПодарочныйСертификат Тогда
// Логика проверки
КонецЕсли;
КонецЦикла;
Проанализируем результаты оптимизации:
Придерживайтесь правила: любые проверки реквизитов ссылочных данных для табличных частей и списков должны выполняться пакетно до входа в цикл обработки данных.