Ошибка памяти при выполнении запроса в 1С: как найти и устранить ошибки оптимизации

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

При работе с крупными базами данных на платформе 1С программисты часто сталкиваются с ситуацией, когда запрос, который «вчера работал нормально», внезапно начинает выдавать ошибку нехватки памяти (Out of Memory), даже если серверная часть запущена в 64-битном режиме. Проблема в том, что 64 бита дают доступ к большему объему памяти, но не защищают от экспоненциального роста объема данных внутри самого SQL-сервера или в оперативной памяти сервера приложений 1С. Разберем на конкретном примере, предоставленном автором темы, какие именно архитектурные ошибки в запросе приводят к такому результату.

Анализ первой временной таблицы: Проблема декартова произведения

Проанализируем первый пакет запроса, где формируется таблица ВтЦеныНоменклатуры. Мы видим следующее соединение:


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют.СрезПоследних(, Валюта = &ВалютаУПР) КАК КурсВалютыУправлУчета
ПО (ИСТИНА)

Использование условия ПО (ИСТИНА) — это явный признак создания декартова произведения (Cross Join). В данном конкретном случае автор рассчитывает, что СрезПоследних вернет одну строку (курс управленческой валюты). Однако, если в параметрах виртуальной таблицы не указана дата или отбор сработал некорректно, SQL-сервер может попытаться перемножить каждую строку из основной таблицы цен на каждую строку таблицы курсов. Рассмотрим ситуацию подробнее: если в ценах 10 000 позиций, а в курсах по какой-то причине оказалось 10 строк, на выходе мы получим 100 000 строк. На больших объемах данных это мгновенно раздувает временные таблицы (TempDB).

Треугольные соединения: Главный пожиратель памяти

Разберем самый опасный участок запроса, который находится в пакете ТаблицаОстатков. Здесь мы видим самосоединение временной таблицы:


ИЗ
    ТабТоварыВРознице КАК ТабТоварыВРознице
    ЛЕВОЕ СОЕДИНЕНИЕ ТабТоварыВРознице КАК ТабТоварыВРознице1
    ПО ТабТоварыВРознице.Склад = ТабТоварыВРознице1.Склад
        И ТабТоварыВРознице.Номенклатура = ТабТоварыВРознице1.Номенклатура
        И ТабТоварыВРознице.Период < ТабТоварыВРознице1.Период

Проанализируем условие ТабТоварыВРознице.Период < ТабТоварыВРознице1.Период. В программировании это называется неравенством в условии соединения (Inequality Join). Вычислительная сложность такой операции составляет O(N²). Выясним причину: для каждой строки из первой таблицы SQL-сервер должен найти все строки из второй таблицы, где дата больше. Если по конкретному товару на складе есть движения за 100 дней, то для первого дня найдется 99 строк, для второго — 98 и так далее. Количество итоговых строк будет равно сумме арифметической прогрессии.

Когда во временной таблице ТабТоварыВРознице находятся тысячи записей, результат такого соединения может исчисляться миллионами или миллиардами строк. Именно на этом этапе чаще всего происходит «вылет» по памяти. SQL-сервер пытается построить огромную промежуточную таблицу в памяти или в TempDB, что приводит к критической ошибке.

Виртуальные таблицы внутри соединений

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

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

Использование выражений ВЫБОР (CASE) в условиях соединения

Рассмотрим еще один проблемный фрагмент в соединении ТабТоварыВРознице и ВтЦеныНоменклатуры:


ПО ТоварыВРозницеОстаткиИОбороты.Номенклатура = ВтЦеныНоменклатуры.Номенклатура
    И (ВЫБОР
        КОГДА ТоварыВРозницеОстаткиИОбороты.Номенклатура.ВестиУчетПоХарактеристикам
            ТОГДА ТоварыВРозницеОстаткиИОбороты.ХарактеристикаНоменклатуры
        ИНАЧЕ ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)
    КОНЕЦ = ВтЦеныНоменклатуры.Характеристика)

Здесь мы видим сразу две серьезные ошибки оптимизации:

  1. Обращение через точку в условии соединения: конструкция Номенклатура.ВестиУчетПоХарактеристикам заставляет 1С неявно присоединять таблицу справочника Номенклатура. Это усложняет план запроса.
  2. CASE в JOIN: использование логических условий ВЫБОР внутри ПО делает невозможным использование Index Seek (поиск по индексу). SQL-сервер будет вынужден выполнять Full Scan (полное сканирование) временных таблиц, что крайне медленно и ресурсозатратно.

Рациональнее заранее подготовить данные во временной таблице ВтЦеныНоменклатуры, чтобы поле Характеристика уже содержало нужные значения для сопоставления, и само соединение шло по простому равенству полей.

Забытая запятая: Скрытое декартово произведение в блоке продаж

Посмотрим на предпоследний пакет запроса (Продажи):


ИЗ
    РегистрНакопления.Продажи.Обороты(...) КАК ПродажиОбороты,
    ВтЦеныНоменклатуры КАК ВтЦеныНоменклатуры

Обратите внимание на отсутствие секции ГДЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ ... ПО .... Это классическое перечисление таблиц через запятую. В мире SQL это означает полное декартово произведение. Каждая продажа будет умножена на каждую цену из таблицы ВтЦеныНоменклатуры. Если у вас 500 продаж и 2000 цен, вы получите 1 000 000 строк. Это грубейшая ошибка, которая гарантированно «подвешивает» систему при росте объема данных.

Рекомендации по рефакторингу запроса

Чтобы исправить этот запрос и избавиться от ошибок памяти, проделаем следующие шаги:

  1. Индексирование временных таблиц: Всегда добавляйте индексы к временным таблицам по тем полям, по которым планируется соединение (Номенклатура, Склад, Характеристика). Это критически важно для работы оптимизатора.
  2. Отказ от треугольных соединений: Вместо самосоединения по условию Период < Период для расчета разности дат, лучше использовать оконные функции (если версия платформы 8.3.20+) или предварительно подготовить таблицу периодов.
  3. Параметризация виртуальных таблиц: Передавайте все возможные отборы (по Номенклатуре, Складу) непосредственно в параметры виртуальных таблиц (в первые скобки), а не в секцию ГДЕ. Это значительно уменьшает объем данных, которые SQL-сервер считывает с диска.
  4. Разделение логики: Не пытайтесь рассчитать средний остаток, курсы валют и продажи в одном гигантском соединении. Разбейте задачу на этапы: сначала соберите остатки, затем — обороты, проиндексируйте их и соедините в финальной выборке.

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

← На главную