Временные таблицы в запросах 1С: где лежат данные и почему падает сервер
Содержание
Менеджер временных таблиц в 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.Выполнить().Выбрать();
Жизненный цикл временной таблицы и связь с сеансом
Временная таблица рождается на ПОМЕСТИТЬ и умирает в одном из четырёх случаев:
- В тексте пакета встретился
УНИЧТОЖИТЬ ВТ_Остатки. - Вызван
МенеджерВТ.Закрыть()— удаляются все таблицы менеджера. - На менеджер больше нет ссылок: закончилась процедура, где он был локальной переменной.
- Запрос выполнялся без внешнего менеджера. Тогда таблицы живут только до конца выполнения этого пакета.
Четвёртый пункт часто путает новичков. Пакет из пяти запросов видит свои ПОМЕСТИТЬ без всякого менеджера. Менеджер нужен, когда таблицу надо передать в другой объект Запрос, в другую процедуру или в функцию общего модуля.
Несколько правил, которые следуют из устройства платформы:
- 🔒 Временная таблица видна только в своём соединении с СУБД. Другой пользователь или фоновое задание её не увидят, даже при совпадении имён.
- 🖥️
МенеджерВременныхТаблицдоступен на сервере, в толстом клиенте и во внешнем соединении. В тонком клиенте и веб-клиенте его нет, поэтому в модуле формы код с ним пишут в процедуре&НаСервере. - ⏳ Между серверными вызовами менеджер не сохраняется. Вызов закончился — переменная ушла, таблицы удалены.
- 🏷️ Имена таблиц удобно начинать с
ВТ_. Так их сразу видно в тексте пакета и не спутать с объектами метаданных. - 🔁 Один менеджер можно передавать параметром в функции. Так типовые конфигурации собирают «цепочки» таблиц, где одна функция готовит данные, а другая их использует.
Опасный сценарий — менеджер, который живёт долго: переменная модуля в длительном фоновом задании или обработка, которая в цикле копит таблицы и не уничтожает их. Пока задание работает, 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С в свойствах кластера есть «Безопасный расход памяти за один вызов» и «Допустимый объём памяти» рабочих процессов. Они не ограничивают временные таблицы напрямую. Но если код выгружает огромную ВТ в таблицу значений, рабочий процесс превысит лимит и будет перезапущен.
Перезапуск рабочего процесса или службы СУБД обрывает сеансы пользователей: несохранённые документы потеряются, открытые транзакции откатятся. Такие действия делают вне рабочего времени или после предупреждения сотрудников.
Индексы и оптимизация запросов с временными таблицами
Временная таблица полезна, когда она заменяет тяжёлый подзапрос в соединении. Оптимизатор СУБД плохо оценивает подзапросы и вложенные виртуальные таблицы. Готовая ВТ с известным числом строк даёт ему точную статистику.
Приёмы, которые реально ускоряют пакет:
- Фильтруйте до
ПОМЕСТИТЬ. Отбор по организации, складу и периоду ставьте в параметры виртуальной таблицы, а не вГДЕследующего запроса. - Берите только нужные поля. Каждое лишнее поле — лишние байты в
tempdb. Ссылочное поле составного типа занимает несколько колонок. - Индексируйте по полям соединения.
ИНДЕКСИРОВАТЬ ПОимеет смысл, если таблица большая (порядок тысяч строк и выше) и дальше участвует в соединении или отборе по этим полям. - Не индексируйте мелочь. Таблица на 50 строк с индексом работает не быстрее: построение индекса тоже стоит времени.
- Уничтожайте отработавшие таблицы в длинных пакетах:
УНИЧТОЖИТЬ ВТ_Движения;после того, как из неё собрали итог. - Не плодите таблицы в цикле. Сто запросов по одной номенклатуре с
ПОМЕСТИТЬмедленнее одного запроса по списку из ста позиций. - Сворачивайте до записи. Если дальше нужен итог по номенклатуре, группируйте в том же запросе, где стоит
ПОМЕСТИТЬ. Миллион строк движений превращается в несколько тысяч строк итогов, иtempdbполучает в сотни раз меньше данных. - Не соединяйте временную таблицу саму с собой без нужды. Нарастающий итог через соединение
ВТс той жеВТпо условию «меньше или равно» даёт квадратичный рост: 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 или строки разной длины, поле становится составным. Соединение по нему работает медленнее и иногда даёт неожиданные пустые строки. Приводите тип явно через ВЫРАЗИТЬ(... КАК Справочник.Номенклатура).