Клиент присылает скриншот: ежемесячный отчёт формируется четыре минуты. Раньше было полторы. Данных стало больше, но не настолько, чтобы время выросло втрое. Подключаемся, открываем запрос — и видим конструкцию, которая встречается в каждом втором отчёте на СКД.
Что за отчёт и почему он важен
Ежемесячная статистика розничных продаж для бухгалтерии: товарооборот по группам, наценка, реализация, себестоимость. Данные из нескольких регистров накопления. На выходе — несколько тысяч строк. Ничего, что должно занимать четыре минуты.
Основной запрос соединял виртуальную таблицу оборотов с регистром движений. LEFT JOIN по четырём полям: продукт, склад, партия, организация. И тут же, в ON-условии, стоял фильтр по периоду — начало и конец отчётного месяца.
Конструкция стандартная. Читается однозначно. На первый взгляд — всё правильно.
Первое подозрение — виртуальная таблица
В 1С параметры виртуальных таблиц критичны для производительности, и ошибка там — классика жанра. Проверили. Параметры заданы корректно: период, организация, склады. Не она.
Открыли план запроса. Table Scan на правой таблице — регистр движений. Сотни тысяч строк читаются целиком, хотя за месяц нужна пара тысяч. Фильтр по периоду стоит. Но не работает.
Проблема не в синтаксисе — в семантике
ON-условие LEFT JOIN работает иначе, чем WHERE. При INNER JOIN оптимизатор SQL Server может «протолкнуть» ограничение внутрь таблицы — сначала отфильтровать, потом соединять. При LEFT JOIN — нет.
Причина фундаментальная: LEFT JOIN гарантирует, что каждая строка левой таблицы попадёт в результат. Если заранее сократить правую таблицу, часть строк левой потеряет пару и получит NULL вместо реальных значений. Оптимизатор не имеет права на такую подмену.
Он обязан прочитать все строки правой таблицы. Все периоды, все организации, все склады. И только потом, при соединении, проверить дату.
Почему LEFT JOIN, а не INNER
LEFT JOIN здесь выбран не случайно: не все товары имеют движения по данному регистру, и потерять их из статистики нельзя. INNER JOIN убрал бы строки, по которым нет совпадений — а это именно те позиции, где бухгалтерии нужно увидеть ноль, а не пустоту.
Типичная ловушка: разработчик знает, что LEFT JOIN нужен для полноты данных, но не учитывает, как это влияет на план запроса. В небольших базах разница незаметна. Когда правая таблица вырастает до сотен тысяч строк — 1С начинает тормозить, и отчёт вместо секунд занимает четыре минуты.
Решение: разделить фильтрацию и соединение
Идея простая: не заставлять LEFT JOIN фильтровать — подготовить данные заранее.
Первый запрос пакета: выбрать из регистра записи за нужный период, отфильтровать через WHERE по организации и складам, сгруппировать, проиндексировать по ключу соединения. Результат — компактная временная таблица с парой тысяч строк.
Второй запрос: тот же LEFT JOIN, но к подготовленной временной таблице вместо сырого регистра.
Тридцать строк кода. Секунды вместо минут.
Как обнаружить эту проблему у себя
Если в вашей системе есть отчёты на СКД с LEFT JOIN — проверьте, где стоят фильтры. Вот чек-лист:
1. Откройте план запроса. В консоли запросов 1С или через SET STATISTICS PROFILE ON на стороне SQL Server. Ищите Table Scan или Clustered Index Scan на правой таблице LEFT JOIN — это признак того, что фильтр не работает.
2. Проверьте ON-условие. Если в нём есть ограничения по периоду, организации, складу — скорее всего, они не используются оптимизатором. Перенесите их в WHERE (если это INNER JOIN) или в предварительный подзапрос (если LEFT JOIN).
3. Оцените объём правой таблицы. Если в ней меньше 10 тысяч строк — разница может быть незаметна. Если сотни тысяч — вынос в временную таблицу почти наверняка поможет.
4. Не забудьте индексировать временную таблицу. В 1С это делается через ИНДЕКСИРОВАТЬ ПО в пакетном запросе. Без индекса соединение двух временных таблиц может оказаться не быстрее исходного запроса.
Почему это не ловится на код-ревью
WHERE ограничивает данные до соединения. ON определяет правило самого соединения. Одно ключевое слово. В документации по SQL это абзац. В ежемесячном отчёте — четыре минуты, пока бухгалтер ждёт.
Проблема в том, что запрос выглядит правильно. Синтаксически — безупречен. Результат — корректный. Просто медленный. Код-ревью без анализа плана запроса это не поймает. А в 1С планы запросов смотрят, мягко говоря, не все.
Мы встречаем эту конструкцию примерно в каждом третьем проекте, куда приходим разбирать производительность. Отчёт работает год-два нормально. Потом данных становится больше — и «внезапно» тормозит. Причина лежала в коде с первого дня. Другие незаметные антипаттерны запросов описаны в статье про четыре антипаттерна запросов 1С. Особенно остро эта проблема проявляется при закрытии месяца в 1С:ERP, где запросы к регистрам накопления выполняются десятки раз подряд. Измерить прирост производительности после оптимизации помогает метрика APDEX.


