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

Программист 1С v8.3 (Обычные формы) Управленческий учет Торговля и дистрибуция
← На главную

Каждый разработчик 1С рано или поздно сталкивается со сценарием, когда небольшой запрос к временной таблице, созданной на основе переданной в параметры ТаблицыЗначений, начинает аномально тормозить. Казалось бы, во временной таблице содержится всего 500–900 строк, однако время выполнения запроса возрастает до катастрофических 10–20 секунд. В условиях высокой интенсивности работы пользователей (например, при выписке сотен ветеринарных свидетельств Меркурия или расходных накладных в день) такие задержки полностью парализуют бизнес-процессы.

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

---

Причина 1: Передача данных между клиентом и сервером (Обычное приложение)

Проанализируем архитектурную специфику обычных форм (Обычного приложения) 1С. В отличие от Управляемого приложения, где разработчик жестко ограничен контекстом выполнения кода, в Обычном приложении код модуля документа по умолчанию выполняется на клиенте.

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

  1. Сериализовать всю таблицу значений Остатки на стороне клиента во внутренний XML-формат передачи данных.
  2. Передать этот огромный объем сериализованных данных по сети с клиентского компьютера на сервер приложений 1С.
  3. На сервере приложений 1С десериализовать полученный поток данных обратно в структуру таблицы значений.
  4. Создать на сервере СУБД физическую временную таблицу (например, в базе данных tempdb MS SQL Server) и заполнить ее строками с помощью SQL-команд INSERT.
  5. Выполнить сам текст запроса.
  6. Передать результат запроса обратно по цепочке: СУБД -> Сервер 1С -> Клиент.

Давайте разберем по шагам, как решить эту проблему с помощью переноса вычислений на сервер.

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

---

Причина 2: Отсутствие жесткой типизации колонок Таблицы Значений

Это одна из самых частых «невидимых» причин медленной работы временных таблиц. Рассмотрим, как СУБД реагирует на нетипизированную таблицу значений.

Если колонки исходной таблицы значений Остатки создаются динамически в коде без явного указания типов (например, простым методом ТЗ.Колонки.Добавить("Номенклатура")), платформа 1С не знает точного типа данных этих колонок. При помещении такой таблицы в запрос СУБД вынуждена создавать колонки во временной таблице с универсальным и крайне ресурсоемким типом sql_variant или длинными текстовыми типами вроде nvarchar(MAX).

Запись даже 500 строк в таблицу с типами sql_variant происходит в СУБД построчно и крайне неоптимально. СУБД тратит много процессорного времени и дискового ввода-вывода на выделение страниц памяти под «тяжелые» типы данных. Именно на этапе наполнения временной таблицы СУБД «зависает» на те самые 10–15 секунд, в то время как сама выборка данных (оператор ВЫБРАТЬ) происходит мгновенно.

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

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


// Создаем описание типов для колонок таблицы значений
МассивТиповНоменклатура = Новый Массив;
МассивТиповНоменклатура.Добавить(Тип("СправочникСсылка.Номенклатура"));
ОписаниеТиповНоменклатура = Новый ОписаниеТипов(МассивТиповНоменклатура);

МассивТиповЧисло = Новый Массив;
МассивТиповЧисло.Добавить(Тип("Число"));
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 3);
ОписаниеТиповЧисло = Новый ОписаниеТипов(МассивТиповЧисло, , , КвалификаторыЧисла);

МассивТиповСтрока = Новый Массив;
МассивТиповСтрока.Добавить(Тип("Строка"));
КвалификаторыСтроки = Новый КвалификаторыСтроки(36); // Ограничиваем длину под GUID
ОписаниеТиповСтрока = Новый ОписаниеТипов(МассивТиповСтрока, , , КвалификаторыСтроки);

// Инициализируем таблицу значений с жесткой типизацией
Остатки = Новый ТаблицаЗначений;
Остатки.Колонки.Добавить("Номенклатура", ОписаниеТиповНоменклатура);
Остатки.Колонки.Добавить("Количество", ОписаниеТиповЧисло);
Остатки.Колонки.Добавить("ГУИДПроизводителя", ОписаниеТиповСтрока);
Остатки.Колонки.Добавить("ДатаВыработки", Новый ОписаниеТипов("Дата"));

Строгое указание типов позволит СУБД создать временную таблицу с оптимальными типами полей (например, uniqueidentifier, numeric(15,3), datetime), что ускорит ее заполнение в сотни раз.

---

Причина 3: Проблемы производительности базы данных TempDB на сервере СУБД

Временные таблицы в СУБД MS SQL Server создаются в системной базе данных tempdb. Если даже простейший запрос вида ВЫБРАТЬ * ПОМЕСТИТЬ Остатки ИЗ &Остатки выполняется неоправданно долго, проблема может крыться в настройках самой СУБД на сервере.

Проанализируем типичные системные проблемы с tempdb:

  1. Медленный дисковый массив: Если база данных tempdb расположена на медленных магнитных дисках (HDD) или делит одну дисковую полку с основной транзакционной базой данных, операции записи во временные таблицы будут упираться в дисковую очередь.
  2. Некорректный шаг авторасширения (Autogrow): Если по умолчанию размер базы tempdb задан очень маленьким (например, 8 МБ), а шаг авторасширения установлен в процентах или небольшим фиксированным объемом (например, 1 МБ), то СУБД при каждом создании временной таблицы будет вынуждена физически приостанавливать работу запроса для расширения файлов на диске.
  3. Конкуренция за системные страницы (PFS и SGAM): На многоядерных серверах при интенсивной работе пользователей с временными таблицами возникает высокая конкуренция за служебные страницы выделения дискового пространства. Если у базы tempdb создан всего один файл данных, это создает бутылочное горлышко.

Решение для системного администратора:

---

Причина 4: Неоптимальный текст запроса и антипаттерны

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

Решение: Динамически формируйте текст запроса в коде 1С. Если параметр (например, &ДатаВыработки или &КодПартии) не заполнен, вообще не добавляйте соответствующее условие в секцию ГДЕ. Очистите условия запроса от функций преобразования типов ВЫРАЗИТЬ.

---

Практический кейс: Способ быстрого обхода проблемы («Сворачивание» данных)

Рассмотрим прагматичное и быстрое решение проблемы, которое применил автор темы (Сообщение 2). Если в текущий момент у вас нет возможности перенастраивать СУБД или переписывать архитектуру вызовов, можно существенно сократить объем передаваемых в запрос данных.

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

Разберем пример реализации этого подхода в коде 1С:


// Создаем структуру отбора по конкретной номенклатуре
Отбор = Новый Структура;
Отбор.Вставить("Номенклатура", НоменклатураДокумента);

// Копируем только подходящие строки во вспомогательную таблицу значений
ОстаткиПоНоменклатуре = ТаблицаОстатковМеркурий.Скопировать(Отбор);

// Передаем в запрос только отфильтрованную таблицу
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	О.ГУИД КАК ГУИД,
    |	О.ДатаВыработки КАК ДатаВыработки,
    |	О.ДатаПросрочки КАК ДатаПросрочки,
    |	О.Количество КАК Количество,
    |	О.Номенклатура КАК Номенклатура,
    |	О.ГУИДПроизводителя КАК ГУИДПроизводителя
    |ПОМЕСТИТЬ ВтОстатки
    |ИЗ
    |	&Остатки КАК О
    |;
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВтОстатки.ГУИД,
    |	ВтОстатки.ДатаВыработки,
    |	ВтОстатки.ДатаПросрочки,
    |	ВтОстатки.Количество
    |ИЗ
    |	ВтОстатки КАК ВтОстатки";

Запрос.УстановитьПараметр("Остатки", ОстаткиПоНоменклатуре);
РезультатЗапроса = Запрос.Выполнить();

Почему это сработало у автора темы: Вместо 900 строк в запрос передавалось всего 5–10 строк, относящихся к конкретной номенклатуре. Объем передаваемых и обрабатываемых СУБД данных сократился в сотни раз, благодаря чему время выполнения запроса упало с 15 секунд до незаметных долей секунды.

---

Резюме и чеклист для оптимизации

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

Чеклист разработчика 1С:

  1. Типизация: Всегда явно описывайте типы колонок таблицы значений перед передачей ее в запрос.
  2. Контекст: Выполняйте запросы только на сервере. Старайтесь формировать исходную таблицу значений на сервере, избегая ее передачи с клиента.
  3. Минимизация данных: Предварительно фильтруйте (сворачивайте, копируйте по отбору) таблицу значений в коде 1С, чтобы передавать в СУБД только необходимый минимум строк.
  4. Отказ от тяжелых условий: Избегайте преобразований типов ВЫРАЗИТЬ и текстовых сравнений ПОДОБНО "%..." в условиях запроса к временным таблицам.
  5. Администрирование СУБД: Разместите базу данных tempdb на скоростных SSD-накопителях, разделите ее на несколько файлов по числу процессорных ядер и настройте фиксированный шаг авторасширения.
← На главную