При разработке в среде «1С:Предприятие» перед программистом регулярно встает задача передачи коллекции значений (например, списка номенклатуры, контрагентов или документов) в параметры запроса для последующей фильтрации с использованием оператора В. На первый взгляд может показаться, что между использованием объекта Массив и объекта СписокЗначений нет принципиальной разницы — запрос успешно отработает в обоих случаях. Однако за этим скрывается целый ряд глубоких архитектурных различий, ограничений платформы, проблем с производительностью и особенностей работы клиент-серверного взаимодействия.
В этой статье мы подробно разберем, почему в абсолютном большинстве случаев для передачи параметров в запрос следует использовать исключительно Массив, в каких ситуациях Список значений может привести к критическим ошибкам или падению производительности, а также рассмотрим внутреннее устройство этих объектов в оперативной памяти.
Давайте проанализируем, как структуры данных Массив и СписокЗначений представлены в оперативной памяти компьютера. Это фундаментальное различие объясняет разницу в скорости их работы и потреблении ресурсов системы.
Массив — это простейшая, максимально облегченная последовательная коллекция элементов. На уровне платформы «1С:Предприятие» массив представляет собой простой индексированный набор переменных. Каждый элемент массива хранит непосредственно само значение (например, ссылку на элемент справочника СправочникСсылка.Номенклатура, число или строку). У массива нет никаких сопутствующих интерфейсных или служебных свойств. Он идеально оптимизирован для быстрого обхода в цикле и передачи по каналам связи.
Список значений — это сложный, специализированный визуальный (интерфейсный) объект. Он создавался в первую очередь для того, чтобы выводить списки пользователю на форму, организовывать интерактивный выбор элементов, устанавливать пометки и связывать элементы с картинками. Из-за этого каждый элемент списка значений является не просто хранимым значением, а тяжелой системной оберткой типа ЭлементСпискаЗначений. Данный вспомогательный объект принудительно содержит в себе следующие свойства:
Картинка, позволяющий сопоставить элементу иконку на экране.Поскольку список значений обременен тяжелыми интерфейсными свойствами, он потребляет в разы больше оперативной памяти, чем массив с аналогичным количеством элементов. Сериализация (перевод объекта в последовательность байт для передачи по сети) и десериализация списка значений занимают значительно больше процессорного времени. Именно поэтому в чисто вычислительном коде, бэкенд-логике и параметрах запросов использование списка значений неэффективно.
Рассмотрим важнейшую особенность работы платформы при использовании динамических списков на управляемых формах. Эта деталь задокументирована в руководстве разработчика 1С, но часто упускается начинающими программистами, приводя к трудноуловимым логическим ошибкам в работе интерфейса.
Если в качестве параметра запроса динамического списка передать объект типа СписокЗначений, то платформа поведет себя следующим образом:
Это поведение является жестким ограничением платформы. Для того чтобы передать множественный отбор в динамический список, разработчик обязан использовать исключительно Массив. При передаче массива платформа корректно разворачивает его во множественное условие на уровне SQL-запроса.
В управляемом приложении 1С код четко разделен на клиентский и серверный уровни. Передача объектов между ними подчиняется строгим правилам сериализации. Рассмотрим классический пример передачи коллекции с клиента на сервер:
&НаСервере
Процедура ВывестиСписокНаСервере(ПереданнаяКоллекция)
Для Каждого Элемент Из ПереданнаяКоллекция Цикл
Сообщить(Элемент.Значение);
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьКоманду(Команда)
Список = Новый СписокЗначений;
Список.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000001"));
Список.Добавить(Справочники.Номенклатура.НайтиПоКоду("00000002"));
ВывестиСписокНаСервере(Список);
КонецПроцедуры
В тонком клиенте управляемого приложения данный код отработает без ошибок. Однако, если запустить этот же код в обычном приложении в клиент-серверном режиме, система вызовет исключение. Список значений в обычном приложении признается мутабельным значением на границе клиент-серверного вызова. Платформа заблокирует передачу такого объекта на сервер для предотвращения несинхронизированного изменения состояния на клиенте и сервере.
Массив же является абсолютно универсальным, немутабельным (с точки зрения неизменности системной структуры) и легковесным контейнером, который гарантированно и без ошибок передается между любыми контекстами выполнения, включая COM-соединения, фоновые задания и веб-сервисы.
Когда мы передаем массив или список в параметр запроса с условием ГДЕ Ссылка В (&Параметр), платформа транслирует этот запрос на уровень СУБД (MS SQL, PostgreSQL, IBM DB2 или Oracle) в виде конструкции IN (значение1, значение2, ... значениеN).
Здесь вступают в силу ограничения самих систем управления базами данных:
В (&Параметр) массив, содержащий более 2100 элементов, платформа или СУБД вернут ошибку выполнения.В превышает 100-200 элементов, оптимизатор СУБД начинает испытывать трудности с построением оптимального плана запроса. Вместо использования эффективного поиска по индексам СУБД может переключиться на полное сканирование таблицы (Index Scan или Table Scan), что приведет к колоссальным задержкам и блокировкам таблиц для других пользователей.Выясним, как правильно поступать, если нам необходимо передать большой объем данных для фильтрации в запросе. Вместо передачи гигантского массива напрямую в параметр условия В, правильным архитектурным решением является использование Временных таблиц.
Рассмотрим по шагам, как реализовать этот оптимальный механизм:
// Шаг 1. Создаем типизированную Таблицу Значений
ТЗПараметров = Новый ТаблицаЗначений;
ТЗПараметров.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
// Шаг 2. Заполняем таблицу значений данными из исходного массива
Для Каждого СсылкаНоменклатуры Из МассивНоменклатуры Цикл
НоваяСтрока = ТЗПараметров.Добавить();
НоваяСтрока.Номенклатура = СсылкаНоменклатуры;
КонецЦикла;
// Шаг 3. Инициализируем Менеджер Временных Таблиц и Запрос
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Шаг 4. Помещаем таблицу значений во временную таблицу и индексируем её
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Данные.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Фильтр
|ИЗ
| &ТЗПараметров КАК ВТ_Данные
|;
|////////////////////////////////////////////////////////////////////////////////
|ИНДЕКСИРОВАТЬ ВТ_Фильтр.Номенклатура
|;";
Запрос.УстановитьПараметр("ТЗПараметров", ТЗПараметров);
Запрос.Выполнить();
// Шаг 5. Выполняем основной запрос, соединяясь с временной таблицей
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК ВТ_Фильтр
| ПО ТоварыНаСкладахОстатки.Номенклатура = ВТ_Фильтр.Номенклатура";
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Благодаря такому подходу СУБД легко строит оптимальный план выполнения запроса, используя индексы временной таблицы и реальной таблицы остатков регистра. Это гарантирует максимальную производительность даже при обработке десятков тысяч позиций.
Чтобы избежать ошибок при проектировании систем и написании программного кода, зафиксируем простые и понятные правила использования коллекций в экосистеме 1С:
Соблюдение этих несложных правил позволит вашему коду работать быстро, стабильно и избавит от неожиданных багов при переходе конфигурации с обычных форм на управляемые или при масштабировании базы данных.