В практике разработки на платформе 1С нередко встречается ситуация, когда идеально работающий на серверной базе запрос при переходе на файловый вариант (например, для разработки или в небольших филиалах) превращается в «бесконечный». Система просто замирает, не выдавая ошибок, но и не завершая выполнение. В данной статье мы подробно разберем, почему это происходит, проанализируем внутренние механизмы файлового движка и найдем эффективные способы оптимизации.
Рассмотрим ситуацию на примере запроса в конфигурации Управление торговлей 11.5. Задача: получить последнюю установленную цену на серию номенклатуры. Проблема локализуется в моменте соединения регистра сведений ЦеныНоменклатуры25 и справочника СерииНоменклатуры. Несмотря на небольшой объем данных (около 800 записей в ценах и 13 000 в сериях), файловая база уходит в глубокое зависание.
Проанализируем исходный кусок запроса, вызывающий проблему:
ВЫБРАТЬ
ЦеныНоменклатуры25.Номенклатура КАК Номенклатура,
МАКСИМУМ(ЕСТЬNULL(СерииНоменклатуры.Партия.Дата, ДАТАВРЕМЯ(1, 1, 1))) КАК ДатаВводаСерии,
МАКСИМУМ(ЦеныНоменклатуры25.Период) КАК Период
ПОМЕСТИТЬ втМаксимальныеДаты
ИЗ
РегистрСведений.ЦеныНоменклатуры25 КАК ЦеныНоменклатуры25
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СерииНоменклатуры КАК СерииНоменклатуры
ПО (ЦеныНоменклатуры25.СерияЦО = СерииНоменклатуры.СерияНоменклатурыДляЦенообразования)
ГДЕ
ЦеныНоменклатуры25.ВидЦены.Наименование = "Закупочные (ССР)"
И ЦеныНоменклатуры25.Цена <> 0
СГРУППИРОВАТЬ ПО
ЦеныНоменклатуры25.Номенклатура
Выясним главную причину такого поведения. SQL Server обладает мощным оптимизатором, который при отсутствии индекса может использовать алгоритмы Hash Join (создание временной хэш-таблицы в памяти) или Merge Join. Это позволяет выполнить соединение за секунды даже на «сырых» данных.
Однако файловый движок 1С — это гораздо более простая СУБД. Она практически всегда использует алгоритм Nested Loops (вложенные циклы). Это означает, что для каждой из 800 строк цен система физически перебирает 13 000 строк серий в поисках соответствия. В итоге получается более 10 миллионов операций сравнения. Без индексов по полям СерияЦО и СерияНоменклатурыДляЦенообразования поиск каждой записи превращается в полное сканирование таблицы, что и создает эффект «мертвого» зависания.
Решение: Установите свойство Индексировать в значение Индексировать (или Индексировать с доп. упорядочиванием) для всех реквизитов, которые участвуют в секции ПО (связи таблиц).
Проанализируем конструкцию СерииНоменклатуры.Партия.Дата. В терминах платформы 1С это называется разыменованием. На уровне базы данных это превращается в еще одно неявное ЛЕВОЕ СОЕДИНЕНИЕ с таблицей документа, который является владельцем партии.
В файловом режиме каждое такое обращение в блоке МАКСИМУМ заставляет движок подтягивать данные из тяжелых таблиц документов для каждой строки, прошедшей первичный фильтр. Если поле Партия имеет составной тип, ситуация усугубляется многократно, так как 1С будет пытаться соединиться со всеми возможными таблицами типов, входящих в этот составной тип.
Рассмотрим более правильный подход с использованием параметров и предварительной подготовки данных:
// Вместо обращения через точку, сначала выберем данные во временную таблицу
ВЫБРАТЬ
Серии.Ссылка КАК Серия,
Серии.СерияНоменклатурыДляЦенообразования КАК СерияЦО,
ВЫРАЗИТЬ(Серии.Партия КАК Документ.ПриобретениеТоваровУслуг).Дата КАК ДатаПартии
ПОМЕСТИТЬ втПодготовленныеСерии
ИЗ
Справочник.СерииНоменклатуры КАК Серии
ГДЕ
НЕ Серии.ПометкаУдаления
ИНДЕКСИРОВАТЬ ПО
СерияЦО
;
Обратим внимание на строку ЦеныНоменклатуры25.ВидЦены.Наименование = "Закупочные (ССР)". Поиск по строковому наименованию — это крайне неэффективная операция для файлового движка. Она не использует индексы ссылочных полей и заставляет систему выполнять сравнение строк.
Рассмотрим, как оптимизировать этот момент: всегда передавайте ссылку на объект через параметр запроса. Это позволит СУБД использовать внутренний индекс по уникальному идентификатору (GUID).
// Правильный вариант условия
ГДЕ
ЦеныНоменклатуры25.ВидЦены = &ВидЦеныПараметр
И ЦеныНоменклатуры25.Цена <> 0
Если реквизит (например, Партия в справочнике серий) добавлен через расширение, нужно учитывать, что в файловой базе 1С данные расширений хранятся в отдельных внутренних таблицах. При выполнении запроса движок делает скрытое соединение основной таблицы и таблицы расширения.
Если в самом расширении для этого реквизита не включено индексирование, то любая попытка использовать его в связях или условиях приведет к катастрофическому падению производительности именно в файловом варианте, так как оптимизатор файловой базы хуже «пробрасывает» индексы между основными и дополнительными таблицами расширений.
Разберем по шагам, как следует переработать запрос для стабильной работы в любом режиме:
вт1, затем из второй во вт2.ИНДЕКСИРОВАТЬ ПО для тех полей, по которым в дальнейшем будет идти соединение.ПО только простые равенства проиндексированных полей. Все дополнительные фильтры (по ценам, пометкам удаления и т.д.) выносите в секцию ГДЕ при заполнении временных таблиц.ВЫРАЗИТЬ(... КАК ...), чтобы ограничить количество таблиц, к которым будет обращаться запрос.Пример оптимизированной структуры пакета запросов:
ВЫБРАТЬ
Цены.Номенклатура КАК Номенклатура,
Цены.СерияЦО КАК СерияЦО,
Цены.Период КАК Период
ПОМЕСТИТЬ втЦены
ИЗ
РегистрСведений.ЦеныНоменклатуры25 КАК Цены
ГДЕ
Цены.ВидЦены = &ВидЦены
И Цены.Цена <> 0
ИНДЕКСИРОВАТЬ ПО
СерияЦО
;
ВЫБРАТЬ
втЦены.Номенклатура КАК Номенклатура,
МАКСИМУМ(ЕСТЬNULL(Серии.Партия.Дата, ДАТАВРЕМЯ(1, 1, 1))) КАК ДатаВводаСерии,
МАКСИМУМ(втЦены.Период) КАК Период
ИЗ
втЦены КАК втЦены
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СерииНоменклатуры КАК Серии
ПО втЦены.СерияЦО = Серии.СерияНоменклатурыДляЦенообразования
СГРУППИРОВАТЬ ПО
втЦены.Номенклатура
Проанализировав ситуацию, мы видим, что даже простое добавление индексов на поля связи в метаданных конфигурации способно сократить время выполнения запроса с «бесконечности» до нескольких секунд. Помните, что файловая база крайне чувствительна к качеству написания кода, и то, что «прощает» мощный SQL Server, станет критической ошибкой в небольшом офисе на файловом ресурсе.