Временные таблицы в запросах 1С: где лежат данные и почему падает сервер

· 10 мин

Содержание

Менеджер временных таблиц в 1С — это объект МенеджерВременныхТаблиц, который держит промежуточные выборки между запросами. Сами строки он у себя не хранит: в клиент-серверной базе они лежат во временных таблицах СУБД (tempdb у MS SQL, временные таблицы сеанса у PostgreSQL), в файловой — в памяти процесса и во временных файлах.

Отсюда главный вывод для производительности. Медленный пакет с временными таблицами почти всегда упирается не в 1С, а в диск и настройки СУБД. А «падение сервера» чаще всего означает переполненный tempdb, забитый %TEMP% или менеджер, который живёт дольше, чем нужно.

Как менеджер временных таблиц хранит данные: память или диск

Менеджер работает как реестр имён. Он помнит, что в текущем соединении есть таблица ВТ_Остатки, и знает, какой физический объект СУБД ей соответствует. Данные передаются между запросами, не покидая сервер СУБД.

Где физически оказываются строки:

Вариант базыГде лежит временная таблицаЧто заканчивается первым
Файловая (1Cv8.1CD)память процесса, который исполняет запрос, при большом объёме — временные файлы в %TEMP%оперативная память клиента или место на системном диске
MS SQL Serverобъекты вида #tt1, #tt2 в базе tempdbместо и скорость диска под tempdb
PostgreSQLвременные таблицы сеанса, буферы temp_buffers, затем файлы на дискеместо на томе с данными, раздувание системного каталога

Для клиент-серверной базы противопоставление «RAM или диск» условное. СУБД сначала пишет в буферный кеш, а при нехватке памяти сбрасывает страницы на диск. Поэтому 10 тысяч строк пролетают незаметно, а выборка на 20 миллионов строк превращается в гигабайты записи.

💡 Рабочий процесс rphost строки временной таблицы в свою память не тянет. Память rphost растёт, когда вы выгружаете результат: Выгрузить() в таблицу значений, вывод в отчёт, обход выборки.

Типовая связка выглядит так (имя регистра условное):

МенеджерВТ = Новый МенеджерВременныхТаблиц;

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.УстановитьПараметр("Дата", ТекущаяДатаСеанса());
Запрос.Текст =
"ВЫБРАТЬ
|	Остатки.Номенклатура КАК Номенклатура,
|	Остатки.КоличествоОстаток КАК Количество
|ПОМЕСТИТЬ ВТ_Остатки
|ИЗ
|	РегистрНакопления.ОстаткиТоваров.Остатки(&Дата) КАК Остатки
|
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура";
Запрос.Выполнить();

// Второй запрос видит ВТ_Остатки через тот же менеджер
Запрос2 = Новый Запрос;
Запрос2.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос2.Текст =
"ВЫБРАТЬ
|	ВТ.Номенклатура,
|	ВТ.Количество
|ИЗ
|	ВТ_Остатки КАК ВТ
|ГДЕ
|	ВТ.Количество < 0";
Выборка = Запрос2.Выполнить().Выбрать();

Жизненный цикл временной таблицы и связь с сеансом

Временная таблица рождается на ПОМЕСТИТЬ и умирает в одном из четырёх случаев:

  1. В тексте пакета встретился УНИЧТОЖИТЬ ВТ_Остатки.
  2. Вызван МенеджерВТ.Закрыть() — удаляются все таблицы менеджера.
  3. На менеджер больше нет ссылок: закончилась процедура, где он был локальной переменной.
  4. Запрос выполнялся без внешнего менеджера. Тогда таблицы живут только до конца выполнения этого пакета.

Четвёртый пункт часто путает новичков. Пакет из пяти запросов видит свои ПОМЕСТИТЬ без всякого менеджера. Менеджер нужен, когда таблицу надо передать в другой объект Запрос, в другую процедуру или в функцию общего модуля.

Несколько правил, которые следуют из устройства платформы:

  • 🔒 Временная таблица видна только в своём соединении с СУБД. Другой пользователь или фоновое задание её не увидят, даже при совпадении имён.
  • 🖥️ МенеджерВременныхТаблиц доступен на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет, поэтому в модуле формы код с ним пишут в процедуре &НаСервере.
  • ⏳ Между серверными вызовами менеджер не сохраняется. Вызов закончился — переменная ушла, таблицы удалены.
  • 🏷️ Имена таблиц удобно начинать с ВТ_. Так их сразу видно в тексте пакета и не спутать с объектами метаданных.
  • 🔁 Один менеджер можно передавать параметром в функции. Так типовые конфигурации собирают «цепочки» таблиц, где одна функция готовит данные, а другая их использует.

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

Различия файлового и клиент-серверного варианта

Синтаксис одинаковый, поведение — нет.

Файловая база. Запрос исполняет движок файловой СУБД в процессе, который работает с базой: у толстого и тонкого клиента это сам клиент, у публикации на веб-сервере — процесс веб-сервера. Временные таблицы занимают его память и файлы во временном каталоге пользователя. Если на диске C: осталось 2 ГБ, а пакет кладёт во временную таблицу движения за пять лет, запрос упадёт с ошибкой записи.

Клиент-серверная база . Платформа переводит текст запроса в SQL и создаёт временную таблицу средствами СУБД. В технологическом журнале у MS SQL видно команды вида CREATE TABLE #tt5 и INSERT INTO #tt5. Нагрузка ложится на сервер СУБД, а не на сервер 1С.

Что сравниваемФайловаяКлиент-сервер
Кто исполняетпроцесс клиента или веб-сервераСУБД
Влияние ИНДЕКСИРОВАТЬ ПОзаметно на больших объёмахреальный индекс в СУБД
Где искать место%TEMP% пользователяtempdb / том PostgreSQL
Чем смотретьзамер производительноститехнологический журнал, средства СУБД

У PostgreSQL есть своя особенность. Каждое создание временной таблицы оставляет записи в системном каталоге (pg_class, pg_attribute). При тысячах создаваемых таблиц в час каталог разрастается, и без регулярной очистки (autovacuum) замедляются вообще все запросы, а не только ваш отчёт.

Лимиты и параметры сервера, от которых зависят временные таблицы

Отдельной квоты «сколько временных таблиц можно создать» в 1С нет. Ограничивает инфраструктура.

Со стороны MS SQL:

  • 💾 tempdb — на отдельный быстрый диск, не на системный.
  • 📁 несколько файлов данных одинакового размера: обычно по числу ядер, но не больше 8 на старте. Так снимается конкуренция за служебные страницы.
  • 📈 заранее заданный начальный размер и автоприрост фиксированными мегабайтами, а не процентами.
  • 🔔 мониторинг свободного места: когда tempdb заполнен, падают запросы всех баз на этом экземпляре.

Со стороны PostgreSQL:

  • temp_buffers — сколько памяти сеанс выделяет под временные таблицы до сброса на диск;
  • work_mem — память под сортировки и хеши, которые часто стоят рядом с ВТ в плане;
  • регулярная работа autovacuum, в том числе по системному каталогу.

Со стороны кластера 1С в свойствах кластера есть «Безопасный расход памяти за один вызов» и «Допустимый объём памяти» рабочих процессов. Они не ограничивают временные таблицы напрямую. Но если код выгружает огромную ВТ в таблицу значений, рабочий процесс превысит лимит и будет перезапущен.

Перезапуск рабочего процесса или службы СУБД обрывает сеансы пользователей: несохранённые документы потеряются, открытые транзакции откатятся. Такие действия делают вне рабочего времени или после предупреждения сотрудников.

Индексы и оптимизация запросов с временными таблицами

Временная таблица полезна, когда она заменяет тяжёлый подзапрос в соединении. Оптимизатор СУБД плохо оценивает подзапросы и вложенные виртуальные таблицы. Готовая ВТ с известным числом строк даёт ему точную статистику.

Приёмы, которые реально ускоряют пакет:

  1. Фильтруйте до ПОМЕСТИТЬ. Отбор по организации, складу и периоду ставьте в параметры виртуальной таблицы, а не в ГДЕ следующего запроса.
  2. Берите только нужные поля. Каждое лишнее поле — лишние байты в tempdb. Ссылочное поле составного типа занимает несколько колонок.
  3. Индексируйте по полям соединения. ИНДЕКСИРОВАТЬ ПО имеет смысл, если таблица большая (порядок тысяч строк и выше) и дальше участвует в соединении или отборе по этим полям.
  4. Не индексируйте мелочь. Таблица на 50 строк с индексом работает не быстрее: построение индекса тоже стоит времени.
  5. Уничтожайте отработавшие таблицы в длинных пакетах: УНИЧТОЖИТЬ ВТ_Движения; после того, как из неё собрали итог.
  6. Не плодите таблицы в цикле. Сто запросов по одной номенклатуре с ПОМЕСТИТЬ медленнее одного запроса по списку из ста позиций.
  7. Сворачивайте до записи. Если дальше нужен итог по номенклатуре, группируйте в том же запросе, где стоит ПОМЕСТИТЬ. Миллион строк движений превращается в несколько тысяч строк итогов, и tempdb получает в сотни раз меньше данных.
  8. Не соединяйте временную таблицу саму с собой без нужды. Нарастающий итог через соединение ВТ с той же ВТ по условию «меньше или равно» даёт квадратичный рост: 10 тысяч строк превращаются в 50 миллионов пар.

Отдельный приём — загрузка таблицы значений в запрос. Её передают параметром и сразу кладут во временную таблицу:

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

Колонки такой таблицы значений должны быть типизированы. Если колонку добавили без описания типов, запрос упадёт с ошибкой о недопустимом типе. Решение — при создании колонки указать Новый ОписаниеТипов(...) или скопировать таблицу с явными типами.

СпособКогда выбиратьМинусы
Вложенный запроспростой отбор, маленькие объёмыоптимизатор ошибается в оценке строк
Временная таблица без индексарезультат читается один раз целикомзапись в tempdb
Временная таблица с индексомдальше соединение или отбор по полювремя на построение индекса
Таблица значений в коденужна обработка на встроенном языкепамять rphost, медленная передача в СУБД

Отладка менеджера временных таблиц и поиск узких мест

Первый вопрос при отладке: что лежит в таблице на текущем шаге. В 8.3 у менеджера есть коллекция Таблицы, и содержимое можно получить прямо в отладчике:

ТаблицаВТ = МенеджерВТ.Таблицы.Найти("ВТ_Остатки");
Если ТаблицаВТ <> Неопределено Тогда
	Данные = ТаблицаВТ.ПолучитьДанные().Выгрузить();
	КоличествоСтрок = Данные.Количество();
КонецЕсли;

Выражение МенеджерВТ.Таблицы.Найти("ВТ_Остатки").ПолучитьДанные().Выгрузить() можно вставить в «Вычислить выражение» отладчика и посмотреть строки. Для пакета без внешнего менеджера есть метод Запрос.ВыполнитьПакетСПромежуточнымиДанными(). Он возвращает результаты всех запросов пакета, включая содержимое временных таблиц.

Чтобы найти, какой шаг пакета медленный, включают технологический журнал. Файл logcfg.xml кладут в каталог conf установленной платформы на сервере 1С:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
  <log location="D:\techlog" history="4">
    <event>
      <eq property="name" value="DBMSSQL"/>
    </event>
    <event>
      <eq property="name" value="DBPOSTGRS"/>
    </event>
    <property name="all"/>
  </log>
  <plansql/>
</config>

В журнале ищите события с текстом #tt и большой длительностью. Элемент <plansql/> добавляет план запроса, по нему видно, сканирует ли СУБД временную таблицу целиком.

С полным набором свойств журнал пишет гигабайты за час. Включайте его на время замера, держите history маленьким и кладите каталог на диск с запасом места. После замера удалите или переименуйте logcfg.xml.

Проверить сам tempdb в MS SQL можно стандартными отчётами SQL Server Management Studio по использованию диска. В PostgreSQL — по размеру каталога базы и предупреждениям в логе сервера.

Ошибки при работе с временными таблицами и как их устранить

Диагностика: выберите симптом

Ещё одна неочевидная ошибка — разные типы в одном поле. Если в ПОМЕСТИТЬ через ОБЪЕДИНИТЬ ВСЕ попадают ссылки на справочник и NULL или строки разной длины, поле становится составным. Соединение по нему работает медленнее и иногда даёт неожиданные пустые строки. Приводите тип явно через ВЫРАЗИТЬ(... КАК Справочник.Номенклатура).

Частые вопросы

Можно ли передать менеджер временных таблиц на клиент?
Нет. Объект доступен только на сервере, в толстом клиенте и во внешнем соединении. На клиент передают уже выгруженные данные, например таблицу значений через временное хранилище.
Видят ли временные таблицы другие пользователи?
Нет. Таблица существует в соединении конкретного сеанса. Два пользователя могут одновременно создать ВТ_Остатки без конфликта.
Чем УНИЧТОЖИТЬ отличается от Закрыть()?
УНИЧТОЖИТЬ удаляет одну таблицу внутри текста запроса. Метод Закрыть() удаляет все таблицы менеджера сразу и вызывается из кода.
Нужно ли индексировать каждую временную таблицу?
Нет. Индекс оправдан для крупных таблиц, которые дальше соединяются или фильтруются по индексируемым полям. Для маленьких таблиц он только добавляет время.
Почему в консоли запросов пакет быстрый, а в отчёте медленный?
Чаще всего отличаются параметры: в консоли узкий период или конкретный склад, в отчёте — весь год без отбора. Сравните значения параметров и число строк в каждой временной таблице.