Как устранить тормоза формы списка после перехода на SQL при использовании сложных отборов

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

Перевод базы данных 1С с файлового варианта на клиент-серверный (MS SQL Server или PostgreSQL) часто преподносит неприятные сюрпризы в виде резкого падения производительности. Одной из классических проблем является работа динамических списков (или табличных полей с типом ДокументСписок) в обычных формах, когда в них накладываются программные отборы по большому количеству ссылок. Проанализируем, почему это происходит, и как правильно перестроить механизмы работы программы.

Причина возникновения проблемы: Особенности SQL

В файловом варианте 1С обрабатывает данные во внутренней памяти, и фильтрация по списку значений (даже если в нем сотни элементов) происходит относительно быстро. Однако в клиент-серверном варианте любое условие в отборе превращается в часть SQL-запроса WHERE.

Когда мы используем ВидСравнения.НеВСписке или ВидСравнения.ВСписке, 1С передает SQL-серверу массив уникальных идентификаторов (GUID). Если список содержит тысячи документов, SQL-сервер сталкивается с рядом трудностей:

  1. Ограничение на количество параметров: SQL-сервер имеет лимиты (около 2100 параметров), и хотя 1С умеет обходить это ограничение, создавая временные таблицы, это все равно порождает «тяжелый» план выполнения.
  2. Игнорирование индексов: Оператор NOT IN (соответствует НеВСписке) практически всегда заставляет СУБД выполнять полное сканирование таблицы (Index Scan), вместо быстрого поиска (Index Seek). Это и приводит к тем самым «тормозам», которые заметил автор темы.

Решение 1: Оптимизация формирования списка (временная мера)

Если архитектура не позволяет быстро внести изменения в структуру метаданных, можно попробовать оптимизировать сам запрос, который собирает список ссылок для отбора. Рассмотрим исходный вариант кода из сообщения пользователя:


Запрос = новый запрос;
Запрос.Текст = "ВЫБРАТЬ
| "+ВидДок+"Товары.Ссылка
|ИЗ
| Документ."+ВидДок+".Товары КАК "+ВидДок+"Товары
|
|СГРУППИРОВАТЬ ПО
| "+ВидДок+"Товары.Ссылка
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| "+ВидДок+"Оборудование.Ссылка
|ИЗ
| Документ."+ВидДок+".Оборудование КАК "+ВидДок+"Оборудование
|
|СГРУППИРОВАТЬ ПО
| "+ВидДок+"Оборудование.Ссылка";
Тз = Запрос.Выполнить.Выгрузить();

Проанализируем ситуацию: использование СГРУППИРОВАТЬ ПО внутри объединения может быть избыточным. Для SQL-сервера эффективнее использовать ВЫБРАТЬ РАЗЛИЧНЫЕ (DISTINCT) или собрать ссылки в одну временную таблицу и уже из нее получить уникальные значения.

Также, как показала практика (сообщение 17), переход от логики «Показать всё, кроме документов с товарами» (НеВСписке) к логике «Показать только документы с услугами» (ВСписке) может значительно ускорить работу, если количество документов во втором случае меньше.

Решение 2: Использование индексированного реквизита (рекомендуемый способ)

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

  1. Добавление реквизита: В документе ПоступлениеТоваровУслуг создаем новый реквизит ТолькоУслуги с типом Булево.
  2. Индексирование: Это критически важный шаг. В свойствах реквизита установите значение «Индексировать». Без этого SQL-сервер будет так же медленно перебирать все документы.
  3. Заполнение данных: Для существующих документов в базе необходимо запустить обработку, которая проверит наличие строк в табличных частях Товары и Оборудование и установит флаг ТолькоУслуги = Истина там, где товаров нет.
  4. Поддержка актуальности: В модуле объекта в процедуре ПередЗаписью добавьте логику автоматического обновления этого флага при изменении состава табличных частей.

После этого отбор в форме списка станет максимально простым и быстрым:


ДокументСписок.Отбор.ТолькоУслуги.ВидСравнения = ВидСравнения.Равно;
ДокументСписок.Отбор.ТолькоУслуги.Значение = Истина;
ДокументСписок.Отбор.ТолькоУслуги.Использование = Истина;

Такой запрос SQL-сервер выполнит мгновенно, используя индекс по созданному полю.

Решение 3: Проблема обновления списка при вводе новых данных

При использовании программного списка (Решение 1) часто возникает ошибка: «Введенные данные не отображены в списке, так как не соответствуют отбору». Это происходит потому, что в момент записи нового документа он еще не включен в ваш массив СписокД, сформированный при открытии формы.

Чтобы решить эту проблему в обычных формах, выполним следующие действия:

  1. В форме документа в процедуре ПослеЗаписи добавим оповещение системы: Оповестить("ОбновитьСписокПоступлений");
  2. В форме списка в процедуре ОбработкаОповещения перехватим это событие:
    
    Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
        Если ИмяСобытия = "ОбновитьСписокПоступлений" Тогда
            // Повторно вызываем получение списка документов и устанавливаем отбор
            СписокД = РаботаСДиалогами.ПолучитьСписокДоков("ПоступлениеТоваровУслуг");
            ДокументСписок.Отбор.Ссылка.Значение = СписокД;
        КонецЕсли;
    КонецПроцедуры
    

Важное замечание: Если вы используете Решение 2 (индексированный реквизит), эта проблема исчезнет сама собой, так как документ сразу будет иметь признак ТолькоУслуги = Истина, и динамический список подхватит его автоматически без дополнительных манипуляций с кодом.

Выводы и рекомендации

Проанализировав ситуацию, можно сделать вывод: клиент-серверная архитектура не прощает «ленивых» решений, которые работали в файловом режиме. Основные правила для высокопроизводительных форм списка в 1С на SQL:

← На главную