Как исправить ошибку «Превышен максимальный расход памяти сервера» в запросе СКД 1С

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

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

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

Почему СКД расходует больше памяти, чем Консоль запросов

Проанализируем механику работы двух инструментов платформы 1С:

  1. Консоль запросов выполняет запрос в СУБД ровно в том виде, в котором он написан, и возвращает плоскую таблицу результатов последовательным потоком.
  2. Система компоновки данных (СКД) перед выполнением запроса трансформирует его. В зависимости от настроек полей, группировок, отборов и включенного флага Автозаполнение, СКД генерирует итоговый текст запроса. Если в запросе присутствуют обращения через точку к реквизитам составных типов (например, Регистратор.Контрагент), СКД добавляет множество неявных ЛЕВОЕ СОЕДИНЕНИЕ ко всем возможным таблицам-регистраторам.
  3. Затем СКД считывает весь массив данных в оперативную память процесса сервера 1С (rphost) для выполнения группировок, иерархии и расчета ресурсов, из-за чего срабатывает защитный механизм платформы по ограничению расхода ОЗУ.

Основные ошибки в коде запроса, приводящие к падению памяти

Рассмотрим характерные недочеты в структуре запросов, вызывающие неконтролируемый рост нагрузки:

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

Шаг 1. Явное приведение типов с помощью ВЫРАЗИТЬ

Чтобы избежать разворачивания левых соединений со всеми таблицами документов, применим функцию ВЫРАЗИТЬ для составного поля Регистратор.

Сравним неоптимальный вариант обращения:


ТоварыПереданныеНаКомиссию.Регистратор.Контрагент КАК Контрагент,
НАЧАЛОПЕРИОДА(ТоварыПереданныеНаКомиссию.Регистратор.Дата, НЕДЕЛЯ) КАК ПериодДокумента

И правильный вариант с явным ограничением типа:


ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,
НАЧАЛОПЕРИОДА(ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата, НЕДЕЛЯ) КАК ПериодДокумента

Шаг 2. Использование промежуточных временных таблиц

Разберем по шагам рефакторинг тяжелого пакета запросов. Вынесем операции объединения данных во временную таблицу, а финальное соединение выполним всего один раз в конце.

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

Посмотрим на пример оптимизированного запроса:


ВЫБРАТЬ
	СУММА(Закупки.Количество) КАК Количество,
	Закупки.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Закупки
ИЗ
	РегистрНакопления.Закупки КАК Закупки
СГРУППИРОВАТЬ ПО
	Закупки.АналитикаУчетаНоменклатуры.Номенклатура

ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,
	ТоварыПереданныеНаКомиссию.Регистратор КАК Документ,
	СУММА(ТоварыПереданныеНаКомиссию.Количество) КАК Количество,
	ТоварыПереданныеНаКомиссию.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
	ТИПЗНАЧЕНИЯ(ТоварыПереданныеНаКомиссию.Регистратор) КАК ТипДокумента,
	НАЧАЛОПЕРИОДА(ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата, НЕДЕЛЯ) КАК ПериодДокумента
ПОМЕСТИТЬ ВТ_ПродажиИВозвраты
ИЗ
	РегистрНакопления.ТоварыПереданныеНаКомиссию КАК ТоварыПереданныеНаКомиссию
ГДЕ
	ТоварыПереданныеНаКомиссию.Период <= &КонецПериода
	И ТоварыПереданныеНаКомиссию.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
СГРУППИРОВАТЬ ПО
	ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент,
	ТоварыПереданныеНаКомиссию.Регистратор,
	ТоварыПереданныеНаКомиссию.АналитикаУчетаНоменклатуры.Номенклатура,
	НАЧАЛОПЕРИОДА(ВЫРАЗИТЬ(ТоварыПереданныеНаКомиссию.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата, НЕДЕЛЯ)

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
	ВыручкаИСебестоимостьПродаж.Регистратор.Контрагент,
	ВыручкаИСебестоимостьПродаж.Регистратор,
	СУММА(ВыручкаИСебестоимостьПродаж.Количество),
	ВыручкаИСебестоимостьПродаж.АналитикаУчетаНоменклатуры.Номенклатура,
	ТИПЗНАЧЕНИЯ(ВыручкаИСебестоимостьПродаж.Регистратор),
	НАЧАЛОПЕРИОДА(ВыручкаИСебестоимостьПродаж.Регистратор.Дата, НЕДЕЛЯ)
ИЗ
	РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК ВыручкаИСебестоимостьПродаж
ГДЕ
	ВыручкаИСебестоимостьПродаж.Период <= &КонецПериода
	И ВыручкаИСебестоимостьПродаж.Регистратор ССЫЛКА Документ.ОтчетКомиссионера
СГРУППИРОВАТЬ ПО
	ВыручкаИСебестоимостьПродаж.Регистратор.Контрагент,
	ВыручкаИСебестоимостьПродаж.Регистратор,
	ВыручкаИСебестоимостьПродаж.АналитикаУчетаНоменклатуры.Номенклатура,
	НАЧАЛОПЕРИОДА(ВыручкаИСебестоимостьПродаж.Регистратор.Дата, НЕДЕЛЯ)

ИНДЕКСИРОВАТЬ ПО
	Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Продажи.Контрагент КАК Контрагент,
	Продажи.Документ КАК Документ,
	Продажи.Количество КАК КоличествоПродано,
	Продажи.Номенклатура КАК Номенклатура,
	Продажи.ТипДокумента КАК ТипДокумента,
	Продажи.ПериодДокумента КАК ПериодДокумента,
	ЕСТЬNULL(Закупки.Количество, 0) КАК КоличествоЗакуплено,
	ВЫБОР
		КОГДА ЕСТЬNULL(Закупки.Количество, 0) > 0
			ТОГДА ВЫРАЗИТЬ(Продажи.Количество / Закупки.Количество * 100 КАК ЧИСЛО(10, 2))
		ИНАЧЕ 0
	КОНЕЦ КАК ПроцентПродаж
ИЗ
	ВТ_ПродажиИВозвраты КАК Продажи
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Закупки КАК Закупки
		ПО Продажи.Номенклатура = Закупки.Номенклатура

Шаг 3. Анализ итогового запроса СКД

Проверим, как СКД видоизменяет наш запрос при компоновке макета. Чтобы увидеть финальный SQL-запрос, который отправляется на сервер баз данных:

  1. Откроем специальную Консоль компоновки данных.
  2. Загрузим схему компоновки отчета и сформируем макет компоновки.
  3. Перейдем на вкладку Запрос в сгенерированном макете и проанализируем текст.
  4. Если СКД генерирует избыточные подзапросы, перейдем на вкладку Наборы данных схемы СКД и отключим параметр Автозаполнение, прописав доступные поля и условия вручную.

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

← На главную