Отбор данных в 1С:Предприятии: от условия в форме до запроса к СУБД
Содержание
Фильтр в 1С — это не отдельный механизм, а условие ГДЕ в запросе, который платформа строит за вас. Установили отбор в списке, задали параметр виртуальной таблицы, добавили условие в схему компоновки — результат один: текст запроса уходит в СУБД, и скорость определяется тем, сможет ли она найти строки по индексу или будет перебирать таблицу целиком.
Поэтому два одинаковых по смыслу фильтра могут работать за 0,05 секунды и за две минуты. Разница почти всегда в трёх вещах: по какому полю отбираем, как записано условие и есть ли подходящий индекс. Дальше разберём эти три вещи, а заодно строки, виртуальные таблицы и блокировки.
Как платформа превращает отбор в запрос
Где бы вы ни задали фильтр, он сводится к одному из трёх путей.
- Отбор в динамическом списке. Пользователь включает отбор в форме списка, а разработчик добавляет элемент в
Список.Отборили в фиксированные настройки компоновщика. Платформа дописывает условие к запросу списка. - Запрос на встроенном языке. Условие в секции
ГДЕ, в параметрах виртуальной таблицы или вСОЕДИНЕНИЕ ... ПО. - Система компоновки данных (СКД). Отбор отчёта: пользовательский, из настроек варианта или из схемы. Он тоже превращается в условия запроса.
Дальше платформа переводит запрос 1С в SQL для вашей СУБД. В клиент-серверном варианте это PostgreSQL или Microsoft SQL Server, в файловом — собственный движок. Планом выполнения занимается оптимизатор СУБД: он решает, идти по индексу или читать таблицу подряд.
Из этого следует важное правило. Платформа сама не «ускоряет» фильтр. Она передаёт условие как записано, и если оно написано так, что индекс использовать нельзя, быстрее не станет.
Пример программного отбора в динамическом списке:
ЭлементОтбора = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Склад");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Склад;
ЭлементОтбора.Использование = Истина;
Отбор по реквизиту, который виден в списке только через точку (Контрагент.ИНН), тянет за собой соединение с другой таблицей. Такой фильтр заметно дороже, чем по собственному полю документа.
Индексы: когда условие их использует
Индекс в базе — это отсортированная структура по одному или нескольким полям. Если условие «попадает» в индекс, СУБД идёт к нужным строкам напрямую. Если нет — читает всю таблицу.
В конфигураторе индексирование включается свойством Индексировать у реквизита справочника или документа, а у регистров — у измерений. Платформа сама создаёт индексы по ссылке, по коду и наименованию (если они используются для поиска), по датам и номерам документов, по измерениям регистров. Лишние индексы не бесплатны: каждый замедляет запись и занимает место.
Что нужно знать про условия:
| Запись условия | Индекс используется? | Почему |
|---|---|---|
Дата >= &Нач И Дата < &Кон | Да | Поле «голое», диапазон |
ГОД(Дата) = 2025 | Нет | Функция над полем, индекс по значению поля не помогает |
Контрагент = &Контрагент | Да | Равенство по индексированному полю |
Контрагент <> &Контрагент | Как правило, нет | Выбирается почти вся таблица |
Наименование ПОДОБНО "%втор%" | Нет | Шаблон начинается с процента |
Наименование ПОДОБНО "Втор%" | Обычно да | Известно начало строки |
Составной индекс работает слева направо. Если индекс построен по полям «Организация, Контрагент, Дата», то условие только по Дате его не задействует, а условие по Организации и Контрагенту — задействует.
Типичная правка — убрать функцию с поля и перенести её на параметр:
// Медленно: функция над полем
ГДЕ ГОД(Док.Дата) = 2025
// Быстро: границы периода в параметрах
ГДЕ Док.Дата >= &НачалоГода И Док.Дата <= &КонецГода
Условия ИЛИ по разным полям тоже часто мешают оптимизатору. Если ветки можно разнести, иногда помогает объединение двух запросов через ОБЪЕДИНИТЬ ВСЕ, но проверяйте на реальных объёмах — единого рецепта нет.
Виртуальные таблицы: условие кладём в параметры
Виртуальные таблицы регистров — Остатки, Обороты, ОстаткиИОбороты, СрезПоследних — не хранятся в базе как есть. Платформа считает их на лету из основной таблицы и таблицы итогов. Поэтому место, где вы записали условие, влияет на объём работы.
Сравните два варианта:
// Хуже: считаем остатки по всем складам, потом отсеиваем
ВЫБРАТЬ Ост.Номенклатура, Ост.КоличествоОстаток
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата) КАК Ост
ГДЕ Ост.Склад = &Склад
// Лучше: условие в параметре виртуальной таблицы
ВЫБРАТЬ Ост.Номенклатура, Ост.КоличествоОстаток
ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, Склад = &Склад) КАК Ост
Во втором случае платформа сразу ограничивает выборку по измерению. Для больших регистров разница ощутимая.
Правила для виртуальных таблиц:
- Отборы по измерениям задавайте в параметре «Условие». Условие по ресурсу или реквизиту в параметр лучше не класть — такой отбор нужно применять после расчёта, в
ГДЕ. - Периодичность и период ограничивайте параметрами, а не условием по полю
Периодво внешнемГДЕ. - Для
СрезПоследнихотбирайте по измерениям внутри параметра: так срез строится только для нужных ключей. - Если регистр обороты считает только по части измерений, не запрашивайте лишние — каждое добавленное измерение увеличивает число групп.
Соединение с виртуальной таблицей без отбора в параметрах — частая причина запроса, который вчера работал, а сегодня «завис». Регистр вырос, а остатки по всем складам считаются заново для каждого запуска.
Строковые поля: подобно, регистр и неограниченная длина
Строки — самое коварное место фильтрации.
Неограниченная длина. Реквизит типа «Строка» без ограничения (комментарии, многострочные описания) нельзя напрямую сравнивать в запросе. Платформа выдаст ошибку, а в динамическом списке такое поле нельзя использовать для отбора. Если нужно сравнить, приводят значение: ВЫРАЗИТЬ(Поле КАК СТРОКА(200)). Но такое преобразование выполняется для каждой строки таблицы и индекса не имеет, поэтому на больших таблицах его лучше избегать.
Регистр символов. Платформа ведёт себя так, будто сравнение строк не зависит от регистра: «ООО Ромашка» и «ооо ромашка» равны. Чтобы так работало на стороне СУБД, база создаётся с соответствующей настройкой сортировки (collation) для SQL Server; для PostgreSQL платформа реализует нужное поведение иначе. Разворачивая базу вручную, не меняйте сортировку на «чувствительную к регистру» — часть запросов поведёт себя иначе, чем ожидается.
Оператор ПОДОБНО. Шаблон с процентом в начале исключает индекс. Это значит полный перебор таблицы. На справочнике из 50 тысяч элементов вы этого не заметите, на регистре сведений в десятки миллионов строк — заметите.
Что делать вместо поиска «по вхождению»:
- хранить в отдельном индексируемом реквизите нормализованное значение (например, номер без пробелов и дефисов) и искать по равенству;
- ограничивать поиск по началу строки;
- использовать полнотекстовый поиск 1С там, где нужен именно поиск по смыслу или по любому слову.
Типы данных тоже важны. Сравнение числа со строкой, строки с ссылкой или поля составного типа вынуждает СУБД делать преобразования, и индекс часто теряется. Поля составного типа вообще ухудшают планы, поэтому на них реже строят отборы.
Блокировки и параллельная работа при фильтрации
Сам по себе отбор данных читает таблицу, но в клиент-серверной базе чтение и запись конфликтуют по правилам блокировок.
В режиме управляемых блокировок платформа не накладывает на чтение собственных блокировок 1С, пока вы явно не попросите. Разработчик ставит блокировку на нужные данные через объект БлокировкаДанных и условия по измерениям. Чем точнее условие блокировки, тем меньше пользователей ждут друг друга. Блокировка «по всему регистру» в проведении документа — типичная причина очередей.
На стороне СУБД картина зависит от настроек. Если запрос читает слишком много строк, СУБД может перейти с блокировок строк на блокировку целой таблицы (в SQL Server такое укрупнение блокировок начинается примерно с пяти тысяч заблокированных строк). Тогда один тяжёлый отбор мешает всем, кто пишет в эту же таблицу.
Практические выводы:
- Длинные отчёты по оперативным регистрам лучше строить в моменты низкой нагрузки или по разделённым данным.
- Тяжёлое чтение не держите внутри транзакции записи. Сначала выберите данные, потом открывайте транзакцию.
- Тайм-аут ожидания блокировки и взаимные блокировки (дедлоки) в журнале выглядят как ошибки операций записи, но причина часто в соседнем медленном запросе, который держит ресурс.
Прерывать чужие сеансы и завершать рабочие процессы, чтобы снять блокировку, можно только после согласования: у пользователей оборвутся документы, которые они не успели записать.
Как найти и разобрать медленный отбор
Работайте от симптома к причине, а не наугад.
- Воспроизведите запрос отдельно. Скопируйте текст в консоль запросов и запустите с теми же параметрами. Если он быстрый в консоли, а медленный в форме, ищите проблему в обработчиках формы или в количестве вызовов.
- Найдите самый дорогой запрос. Для этого подойдёт технологический журнал: события обращений к СУБД со временем выполнения показывают, что именно тормозит. Также можно использовать замер производительности, если он подключён в вашей конфигурации.
- Посмотрите план запроса на стороне СУБД. В SQL Server — план выполнения в Management Studio, в PostgreSQL —
EXPLAIN ANALYZE. Ищите полное сканирование таблицы там, где ожидали поиск по индексу. - Проверьте обновление статистики. Устаревшая статистика приводит к плохим планам; для регламентных операций настройте обслуживание базы.
- Исправьте условие, и только потом добавляйте индекс. Сначала уберите функции с полей и лишние соединения, затем решайте, нужен ли новый индекс.
ПОДОБНО "%...%" читает всю таблицу. Перейдите на поиск по началу строки или на полнотекстовый поиск.Частые ошибки при настройке фильтров
- Функция над индексируемым полем.
ГОД,МЕСЯЦ,ПОДСТРОКАв левой части условия отключают индекс. Переносите вычисления в параметры. - Отбор по ресурсу вместо измерения. Регистр не может использовать для ресурса те же индексы, что для измерений. Если отбор нужен часто, возможно, поле стоит сделать измерением.
- Добавление индексов на каждое поле «на всякий случай». Запись документов становится медленнее, размер базы растёт, а выигрыша по запросам нет, если условия записаны неудачно.
- Выборка всех полей и всех записей. Лишние колонки тоже нагрузка. Берите только нужные и ограничивайте число строк словом
ПЕРВЫЕ, когда пользователю не нужен весь результат. - Проверка на
NULLи сравнение сНЕОПРЕДЕЛЕНОоператором равенства. Для этого естьЕСТЬNULLиЕСТЬ NULL, а= НЕОПРЕДЕЛЕНОработает не так, как ожидают.
Частые вопросы
Нужно ли ставить индексирование на каждый реквизит, по которому ищут?
Почему отбор по дате работает быстро, а по месяцу медленно?
Чем отличается условие в параметре виртуальной таблицы от условия в `ГДЕ`?
ГДЕ применяется к уже рассчитанному результату.