Динамический список 1С тормозит: почему форма открывается 30 секунд

Открыли список «Заказы покупателей». Платформа задумалась, индикатор загрузки крутится двадцать восемь секунд. Появилась первая страница. Прокрутили вниз — снова пауза. Поставили отбор по контрагенту — ещё пятнадцать секунд. Это типовая конфигурация, никто в ней не копался, никаких доработок. Всё «как из коробки».

Из коробки оно работало хорошо ровно до того момента, пока в таблице заказов не накопилось два миллиона строк. После этого динамический список начал жить своей жизнью.

Что вообще такое динамический список

Динамический список — это не «таблица, которую я заполняю запросом». Это форма работы с данными, при которой платформа делает запрос за каждой видимой страницей в момент, когда пользователь её прокручивает. Не за всеми данными сразу, а порциями. Это правильный подход для больших объёмов: открыли форму — прочитали 50 строк, прокрутили — прочитали следующие 50.

Платформа для этого делает не один большой запрос, а серию маленьких — с курсором по сортировке. Чтобы курсор работал предсказуемо, СУБД должна уметь быстро находить «следующие N строк после такого-то значения сортировки». Если может — открытие формы занимает миллисекунды независимо от объёма таблицы. Если не может — каждый раз СУБД сканирует половину таблицы, и вы получаете двадцать восемь секунд ожидания.

Поэтому почти все проблемы с динамическими списками — это проблемы курсорного запроса: либо отсутствует индекс по полю сортировки, либо запрос написан так, что курсор СУБД не может его использовать.

Динамический список 1С — порционная загрузка данных и роль курсора СУБД

Пять причин тормозов и что с ними делать

Причина первая: основная таблица не указана. Это самая частая. В свойствах динамического списка есть поле «Основная таблица». Если оно пустое, платформа не может включить динамическое чтение. Она сначала выполнит весь запрос целиком, получит миллионы строк, отсортирует, и только потом отдаст пользователю первые 50. То, что должно работать порциями, превращается в полную выборку. На двух миллионах заказов это занимает полминуты на каждом открытии формы.

Лечение: открыть форму, найти реквизит-список, в свойствах выставить «Основная таблица» = справочник или документ, который вы выбираете. После этого платформа сама строит курсорный запрос. Проверьте на тестовой базе с такими же объёмами — обычно достаточно одного этого изменения.

Причина вторая: сортировка по неиндексированному полю. Платформа умеет курсорно читать только по тем полям, по которым есть индекс. Если пользователь поставил сортировку по «Сумма документа», а индекса по этому полю нет, СУБД делает полную сортировку всех записей перед возвратом курсора. На таблице из миллиона строк это секунды или десятки секунд. Хуже того — на каждом скролле эта сортировка повторяется заново, потому что курсор не может «продолжить» с того места, где остановился.

Лечение: определить, по каким полям пользователи реально сортируют. В типовых заказах это «Дата», «Контрагент», «Сумма». «Дата» обычно индексирована, потому что это поле документа. «Контрагент» — обычно нет. «Сумма» — никогда. Решений два: либо настроить пользователю сортировку только по «Дате» (если для бизнеса это приемлемо), либо добавить индекс по нужным полям. Индексация в 1С возможна через свойство «Индексировать» у реквизита, но не для всех типов данных. Иногда приходится переходить на расширенный отбор через регистр сведений, который индексируется штатно.

Причина третья: вычисляемые поля в основном запросе. Программисты любят добавлять в текст запроса динамического списка «полезные» поля: ВЫБОР КОГДА Документ.СтатусОплаты = ... ТОГДА "Оплачен" ИНАЧЕ "Не оплачен" КОНЕЦ, или СУММА(Движений.Сумма) через подзапрос. Каждое такое поле — это работа, которую СУБД должна сделать на каждой странице при прокрутке.

На пятидесяти строках это незаметно. Но если вычисляемое поле требует подзапроса или соединения с другой таблицей — СУБД делает соединение для всех строк, чтобы корректно отсортировать. И производительность падает в десятки раз. Выносите такие поля в отдельный реквизит документа (заполняемый при записи), либо в обработчик ПриПолученииДанных формы — он вычисляет поле только для тех строк, которые сейчас видит пользователь, а не для всей таблицы.

Причина четвёртая: соединения с большими таблицами. Если в запросе списка есть LEFT JOIN с таблицей движений или с регистром накопления, и этот JOIN не имеет ограничения по периоду — каждый скролл будет читать половину регистра. Решение: добавить условие отбора по диапазону дат прямо в JOIN, либо вынести соединение из основного запроса в обработчик ПриПолученииДанных.

Причина пятая: пользователь поставил отбор по неудобному полю. Это не вина разработчика — это вина настройки. Когда пользователь делает «Установить отбор → Контрагент = Иванов», платформа применяет фильтр на уровне SQL. Если по контрагенту нет индекса, отбор превращается в полное сканирование. Лечение: либо индексация, либо запрет таких отборов через свойство «Использовать отбор» у поля списка.

Сравнение времени открытия динамического списка 1С до и после оптимизации

Как искать причину быстро

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

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

Шаг второй: включить технологический журнал с фильтром по событию DBMSSQL и длительности больше 200 миллисекунд. Повторить те же три действия. В логе появятся длительные SQL-запросы. Возьмите текст самого медленного, посмотрите на WHERE и ORDER BY — это покажет, по каким полям СУБД делает работу.

Шаг третий: выполнить этот SQL вручную в SQL Server Management Studio (или в PostgreSQL pgAdmin) с включённым планом запроса. План покажет, делает ли СУБД seek по индексу или scan. Scan на большой таблице — это и есть ваша проблема.

Шаг четвёртый: проверить свойства реквизита, по которому идёт отбор или сортировка, и убедиться, что у него стоит «Индексировать». Если нет — поставить, обновить базу, проверить ещё раз. В большинстве случаев индекс решает проблему за секунду.

Когда индекс не помогает

Иногда вы добавили индекс, обновили базу, а список всё равно открывается медленно. Это бывает в нескольких случаях.

Первый — индекс есть, но СУБД его не использует, потому что статистика таблицы устарела. После обновления базы или миграции данных нужно пересобрать статистику: в MS SQL это UPDATE STATISTICS, в PostgreSQL — ANALYZE. Регулярное обслуживание базы должно это делать автоматически, и про это есть отдельная статья — настройка SQL Server для 1С.

Второй — индекс есть, но условие в запросе не использует начало индекса. Если индекс составной (Дата, Контрагент), а вы фильтруете только по Контрагенту — индекс не сработает. Нужен либо отдельный индекс по Контрагенту, либо изменение запроса.

Третий — данных в таблице много, но СУБД думает, что мало (или наоборот), и выбирает неоптимальный план. Это лечится принудительным обновлением статистики или, в крайнем случае, хинтом запроса. В 1С хинты ставятся через служебные слова в тексте запроса (ИНДЕКСИРОВАТЬ ПО), но злоупотреблять этим не стоит.

Четвёртый — таблица фрагментирована из-за частых вставок и удалений. Регулярная дефрагментация индексов и пересборка таблиц решает проблему. Это часть штатного регламентного обслуживания SQL-сервера.

Когда нужно отказаться от динамического списка

Иногда правильное решение — не оптимизировать список, а заменить его на что-то другое. Динамический список хорош, когда пользователю нужно видеть «всё, что есть», и иногда искать. Если же сценарий «открыл, поставил три отбора, нашёл нужное» — динамический список не лучший выбор.

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

Вторая — отдельный отчёт на СКД вместо списка. Если пользователь по факту использует список как «отчёт по заказам с фильтрами», лучше так и сделать. У отчётов есть штатная пагинация, кэш, экспорт — то, чего у списка нет.

Третья — вынести часть полей в реквизиты, которые заполняются при записи документа. Например, если на форме списка нужна колонка «Состояние оплаты», и она вычисляется через подзапрос к движениям регистра, — лучше добавить реквизит «СостояниеОплаты» в сам документ и обновлять его при каждой записи документа или при движении регистра. Тогда на форме списка просто читается значение реквизита, без подзапросов.

Что нужно проверить прямо сейчас

Если вы дочитали до этого места, у вас в голове наверняка крутится конкретная форма, которая открывается долго. Проверьте по списку.

Открыли форму со списком, посмотрели в свойствах реквизита, есть ли «Основная таблица». Если нет — это первое, что чинить.

Посмотрели текст запроса динамического списка. Если в нём есть подзапросы, вычисляемые поля типа ВЫБОР, соединения с регистрами — каждый из этих элементов проверить на «нужно ли это в основном запросе или можно вынести».

Посмотрели, какие реквизиты пользователи могут использовать для отбора и сортировки. Для каждого такого реквизита — проверили в Конфигураторе свойство «Индексировать». Если у вас регистр сведений с документами — отдельно проверили индексы на регистре.

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

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