Отбор данных в 1С:Предприятии: от условия в форме до запроса к СУБД

· 9 мин

Содержание

Фильтр в 1С — это не отдельный механизм, а условие ГДЕ в запросе, который платформа строит за вас. Установили отбор в списке, задали параметр виртуальной таблицы, добавили условие в схему компоновки — результат один: текст запроса уходит в СУБД, и скорость определяется тем, сможет ли она найти строки по индексу или будет перебирать таблицу целиком.

Поэтому два одинаковых по смыслу фильтра могут работать за 0,05 секунды и за две минуты. Разница почти всегда в трёх вещах: по какому полю отбираем, как записано условие и есть ли подходящий индекс. Дальше разберём эти три вещи, а заодно строки, виртуальные таблицы и блокировки.

Как платформа превращает отбор в запрос

Где бы вы ни задали фильтр, он сводится к одному из трёх путей.

  • Отбор в динамическом списке. Пользователь включает отбор в форме списка, а разработчик добавляет элемент в Список.Отбор или в фиксированные настройки компоновщика. Платформа дописывает условие к запросу списка.
  • Запрос на встроенном языке. Условие в секции ГДЕ, в параметрах виртуальной таблицы или в СОЕДИНЕНИЕ ... ПО.
  • Система компоновки данных (СКД). Отбор отчёта: пользовательский, из настроек варианта или из схемы. Он тоже превращается в условия запроса.

Дальше платформа переводит запрос 1С в SQL для вашей СУБД. В клиент-серверном варианте это PostgreSQL или Microsoft SQL Server, в файловом — собственный движок. Планом выполнения занимается оптимизатор СУБД: он решает, идти по индексу или читать таблицу подряд.

Из этого следует важное правило. Платформа сама не «ускоряет» фильтр. Она передаёт условие как записано, и если оно написано так, что индекс использовать нельзя, быстрее не станет.

Пример программного отбора в динамическом списке:

ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение  = Новый ПолеКомпоновкиДанных("Склад");
ЭлементОтбора.ВидСравнения   = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Склад;
ЭлементОтбора.Использование  = Истина;

Отбор по реквизиту, который виден в списке только через точку (Контрагент.ИНН), тянет за собой соединение с другой таблицей. Такой фильтр заметно дороже, чем по собственному полю документа.

Индексы: когда условие их использует

Индекс в базе — это отсортированная структура по одному или нескольким полям. Если условие «попадает» в индекс, СУБД идёт к нужным строкам напрямую. Если нет — читает всю таблицу.

В конфигураторе индексирование включается свойством Индексировать у реквизита справочника или документа, а у регистров — у измерений. Платформа сама создаёт индексы по ссылке, по коду и наименованию (если они используются для поиска), по датам и номерам документов, по измерениям регистров. Лишние индексы не бесплатны: каждый замедляет запись и занимает место.

Что нужно знать про условия:

Запись условияИндекс используется?Почему
Дата >= &Нач И Дата < &КонДаПоле «голое», диапазон
ГОД(Дата) = 2025НетФункция над полем, индекс по значению поля не помогает
Контрагент = &КонтрагентДаРавенство по индексированному полю
Контрагент <> &КонтрагентКак правило, нетВыбирается почти вся таблица
Наименование ПОДОБНО "%втор%"НетШаблон начинается с процента
Наименование ПОДОБНО "Втор%"Обычно даИзвестно начало строки

Составной индекс работает слева направо. Если индекс построен по полям «Организация, Контрагент, Дата», то условие только по Дате его не задействует, а условие по Организации и Контрагенту — задействует.

Типичная правка — убрать функцию с поля и перенести её на параметр:

// Медленно: функция над полем
ГДЕ ГОД(Док.Дата) = 2025

// Быстро: границы периода в параметрах
ГДЕ Док.Дата >= &НачалоГода И Док.Дата <= &КонецГода

Условия ИЛИ по разным полям тоже часто мешают оптимизатору. Если ветки можно разнести, иногда помогает объединение двух запросов через ОБЪЕДИНИТЬ ВСЕ, но проверяйте на реальных объёмах — единого рецепта нет.

Виртуальные таблицы: условие кладём в параметры

Виртуальные таблицы регистров — Остатки, Обороты, ОстаткиИОбороты, СрезПоследних — не хранятся в базе как есть. Платформа считает их на лету из основной таблицы и таблицы итогов. Поэтому место, где вы записали условие, влияет на объём работы.

Сравните два варианта:

// Хуже: считаем остатки по всем складам, потом отсеиваем
ВЫБРАТЬ Ост.Номенклатура, Ост.КоличествоОстаток
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Ост
ГДЕ Ост.Склад = &Склад

// Лучше: условие в параметре виртуальной таблицы
ВЫБРАТЬ Ост.Номенклатура, Ост.КоличествоОстаток
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Ост

Во втором случае платформа сразу ограничивает выборку по измерению. Для больших регистров разница ощутимая.

Правила для виртуальных таблиц:

  1. Отборы по измерениям задавайте в параметре «Условие». Условие по ресурсу или реквизиту в параметр лучше не класть — такой отбор нужно применять после расчёта, в ГДЕ.
  2. Периодичность и период ограничивайте параметрами, а не условием по полю Период во внешнем ГДЕ.
  3. Для СрезПоследних отбирайте по измерениям внутри параметра: так срез строится только для нужных ключей.
  4. Если регистр обороты считает только по части измерений, не запрашивайте лишние — каждое добавленное измерение увеличивает число групп.

Соединение с виртуальной таблицей без отбора в параметрах — частая причина запроса, который вчера работал, а сегодня «завис». Регистр вырос, а остатки по всем складам считаются заново для каждого запуска.

Строковые поля: подобно, регистр и неограниченная длина

Строки — самое коварное место фильтрации.

Неограниченная длина. Реквизит типа «Строка» без ограничения (комментарии, многострочные описания) нельзя напрямую сравнивать в запросе. Платформа выдаст ошибку, а в динамическом списке такое поле нельзя использовать для отбора. Если нужно сравнить, приводят значение: ВЫРАЗИТЬ(Поле КАК СТРОКА(200)). Но такое преобразование выполняется для каждой строки таблицы и индекса не имеет, поэтому на больших таблицах его лучше избегать.

Регистр символов. Платформа ведёт себя так, будто сравнение строк не зависит от регистра: «ООО Ромашка» и «ооо ромашка» равны. Чтобы так работало на стороне СУБД, база создаётся с соответствующей настройкой сортировки (collation) для SQL Server; для PostgreSQL платформа реализует нужное поведение иначе. Разворачивая базу вручную, не меняйте сортировку на «чувствительную к регистру» — часть запросов поведёт себя иначе, чем ожидается.

Оператор ПОДОБНО. Шаблон с процентом в начале исключает индекс. Это значит полный перебор таблицы. На справочнике из 50 тысяч элементов вы этого не заметите, на регистре сведений в десятки миллионов строк — заметите.

Что делать вместо поиска «по вхождению»:

  • хранить в отдельном индексируемом реквизите нормализованное значение (например, номер без пробелов и дефисов) и искать по равенству;
  • ограничивать поиск по началу строки;
  • использовать полнотекстовый поиск 1С там, где нужен именно поиск по смыслу или по любому слову.

Типы данных тоже важны. Сравнение числа со строкой, строки с ссылкой или поля составного типа вынуждает СУБД делать преобразования, и индекс часто теряется. Поля составного типа вообще ухудшают планы, поэтому на них реже строят отборы.

Блокировки и параллельная работа при фильтрации

Сам по себе отбор данных читает таблицу, но в клиент-серверной базе чтение и запись конфликтуют по правилам блокировок.

В режиме управляемых блокировок платформа не накладывает на чтение собственных блокировок 1С, пока вы явно не попросите. Разработчик ставит блокировку на нужные данные через объект БлокировкаДанных и условия по измерениям. Чем точнее условие блокировки, тем меньше пользователей ждут друг друга. Блокировка «по всему регистру» в проведении документа — типичная причина очередей.

На стороне СУБД картина зависит от настроек. Если запрос читает слишком много строк, СУБД может перейти с блокировок строк на блокировку целой таблицы (в SQL Server такое укрупнение блокировок начинается примерно с пяти тысяч заблокированных строк). Тогда один тяжёлый отбор мешает всем, кто пишет в эту же таблицу.

Практические выводы:

  • Длинные отчёты по оперативным регистрам лучше строить в моменты низкой нагрузки или по разделённым данным.
  • Тяжёлое чтение не держите внутри транзакции записи. Сначала выберите данные, потом открывайте транзакцию.
  • Тайм-аут ожидания блокировки и взаимные блокировки (дедлоки) в журнале выглядят как ошибки операций записи, но причина часто в соседнем медленном запросе, который держит ресурс.

Прерывать чужие сеансы и завершать рабочие процессы, чтобы снять блокировку, можно только после согласования: у пользователей оборвутся документы, которые они не успели записать.

Как найти и разобрать медленный отбор

Работайте от симптома к причине, а не наугад.

  1. Воспроизведите запрос отдельно. Скопируйте текст в консоль запросов и запустите с теми же параметрами. Если он быстрый в консоли, а медленный в форме, ищите проблему в обработчиках формы или в количестве вызовов.
  2. Найдите самый дорогой запрос. Для этого подойдёт технологический журнал: события обращений к СУБД со временем выполнения показывают, что именно тормозит. Также можно использовать замер производительности, если он подключён в вашей конфигурации.
  3. Посмотрите план запроса на стороне СУБД. В SQL Server — план выполнения в Management Studio, в PostgreSQL — EXPLAIN ANALYZE. Ищите полное сканирование таблицы там, где ожидали поиск по индексу.
  4. Проверьте обновление статистики. Устаревшая статистика приводит к плохим планам; для регламентных операций настройте обслуживание базы.
  5. Исправьте условие, и только потом добавляйте индекс. Сначала уберите функции с полей и лишние соединения, затем решайте, нужен ли новый индекс.
Диагностика: выберите симптом

Частые ошибки при настройке фильтров

  • Функция над индексируемым полем. ГОД, МЕСЯЦ, ПОДСТРОКА в левой части условия отключают индекс. Переносите вычисления в параметры.
  • Отбор по ресурсу вместо измерения. Регистр не может использовать для ресурса те же индексы, что для измерений. Если отбор нужен часто, возможно, поле стоит сделать измерением.
  • Добавление индексов на каждое поле «на всякий случай». Запись документов становится медленнее, размер базы растёт, а выигрыша по запросам нет, если условия записаны неудачно.
  • Выборка всех полей и всех записей. Лишние колонки тоже нагрузка. Берите только нужные и ограничивайте число строк словом ПЕРВЫЕ, когда пользователю не нужен весь результат.
  • Проверка на NULL и сравнение с НЕОПРЕДЕЛЕНО оператором равенства. Для этого есть ЕСТЬNULL и ЕСТЬ NULL, а = НЕОПРЕДЕЛЕНО работает не так, как ожидают.

Частые вопросы

Нужно ли ставить индексирование на каждый реквизит, по которому ищут?
Нет. Включайте его только для реквизитов, по которым отбор идёт часто и по прямому сравнению или началу строки. Каждый индекс замедляет запись.
Почему отбор по дате работает быстро, а по месяцу медленно?
В первом случае условие задаёт границы периода по самому полю. Во втором оно вызывает функцию над полем, и индекс по дате не используется.
Чем отличается условие в параметре виртуальной таблицы от условия в `ГДЕ`?
Условие в параметре платформа применяет при расчёте таблицы, и считается меньше данных. Условие в ГДЕ применяется к уже рассчитанному результату.
Можно ли отбирать в запросе по строке неограниченной длины?
Напрямую нет. Значение приходится приводить к строке ограниченной длины, но это дорого на больших таблицах. Лучше хранить отдельный короткий реквизит для поиска.
Помогает ли обновление платформы ускорить фильтры?
Иногда: меняются планы запросов и оптимизации. Но условие, которое нельзя выполнить по индексу, остаётся медленным в любой версии.