В практике разработчика 1С иногда возникают ситуации, которые кажутся парадоксальными: элементарный запрос, возвращающий чуть более тысячи записей, успешно отрабатывает в консоли запросов, но при запуске в программном модуле вызывает критическую ошибку «Недостаточно свободной памяти для выполнения операции». При этом аппаратные ресурсы сервера могут быть практически неограниченными. В этой статье мы подробно разберем, почему это происходит, проанализируем внутренние механизмы платформы и найдем эффективные способы решения проблемы.
Рассмотрим типичный пример кода, который вызывает подобный сбой. Задача кажется простой: выбрать документы за период и сгруппировать их по дням для последующей обработки.
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| НАЧАЛОПЕРИОДА(ЗакупкаВинограда.Дата, ДЕНЬ) КАК ДатаДок,
| ЗакупкаВинограда.Ссылка КАК Ссылка
|ИЗ
| Документ.ЗакупкаВинограда КАК ЗакупкаВинограда
|ГДЕ
| ЗакупкаВинограда.Проведен = ИСТИНА
| И ЗакупкаВинограда.ПометкаУдаления = ЛОЖЬ
| И ЗакупкаВинограда.Дата МЕЖДУ &ДатаНач И &ДатаКон
|УПОРЯДОЧИТЬ ПО
| ЗакупкаВинограда.Номер
|ИТОГИ ПО
| ДатаДок";
Запрос.УстановитьПараметр("ДатаНач", НачДата);
Запрос.УстановитьПараметр("ДатаКон", КонДата);
Результат = Запрос.Выполнить();
ВыборкаДаты = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "ДатаДок");
Проблема проявляется именно на этапе вызова метода Выбрать() или в процессе обхода выборки. Как показывает практика, «магическим» числом часто становится 1000 записей: до этого порога всё работает идеально, но как только результат превышает это количество, платформа выбрасывает исключение.
Проанализируем, как 1С работает с конструкцией ИТОГИ. Когда мы используем ОбходРезультатаЗапроса.ПоГруппировкам, платформа не просто получает плоский набор данных, а строит в памяти сложную древовидную структуру объектов. Каждый узел этой структуры требует выделения дополнительной памяти под служебные данные.
В отличие от метода Выгрузить(), который создает таблицу значений (линейную структуру), метод Выбрать() ориентирован на динамическую работу с курсором. Однако при наличии итогов 1С пытается подготовить данные для удобного перехода между уровнями иерархии. Если в выборке много ссылочных данных, объем потребляемой памяти растет не линейно, а экспоненциально из-за необходимости кэширования представлений и внутренних идентификаторов.
Выясним важный технический нюанс: даже если на вашем сервере установлено 128 ГБ оперативной памяти, клиентское приложение 1С (особенно если используется 32-битная версия 1cv8.exe) имеет жесткое ограничение адресного пространства в 2 ГБ. На практике ошибка «Недостаточно памяти» может возникнуть уже при достижении процессом порога в 1.5–1.8 ГБ.
Иерархическая выборка с итогами по ссылочным полям потребляет ресурсы именно на стороне клиентского сеанса (или в памяти рабочего процесса сервера, если код выполняется на стороне сервера). Если платформа сталкивается с утечкой памяти (что было замечено в некоторых релизах ветки 8.3.13), лимит исчерпывается мгновенно.
При обходе выборки, где есть поле Ссылка, система часто неявно пытается получить Представление этого объекта. Если в конфигурации переопределен модуль ОбработкаПолученияПредставления или документ имеет сложную логику формирования наименования, получение представлений для 1000+ записей внутри иерархического обхода может привести к лавинообразному расходу памяти.
Самый надежный способ оптимизации — отказаться от использования ИТОГИ в тексте запроса, если иерархия не является жизненно необходимой. Вместо построения дерева в памяти, мы можем использовать обычную группировку СГРУППИРОВАТЬ ПО или просто упорядочивание, а логику иерархии реализовать программно.
Рассмотрим пример переписанного запроса:
Запрос.Текст = "ВЫБРАТЬ
| НАЧАЛОПЕРИОДА(ЗакупкаВинограда.Дата, ДЕНЬ) КАК ДатаДок,
| ЗакупкаВинограда.Ссылка КАК Ссылка
|ИЗ
| Документ.ЗакупкаВинограда КАК ЗакупкаВинограда
|ГДЕ
| ЗакупкаВинограда.Проведен = ИСТИНА
|УПОРЯДОЧИТЬ ПО
| ДатаДок,
| ЗакупкаВинограда.Номер";
В коде обхода мы просто проверяем смену даты в цикле. Это работает в десятки раз быстрее и потребляет минимум памяти.
Если памяти не хватает именно в момент формирования выборки, попробуем выгрузить результат запроса в таблицу значений. Таблица значений хранится в памяти более компактно, чем дерево итогов.
Результат = Запрос.Выполнить();
ТаблицаДанных = Результат.Выгрузить();
Для Каждого Строка Из ТаблицаДанных Цикл
// Обработка данных
КонецЦикла;
Метод Выгрузить() создает «плоский» слепок данных, что исключает накладные расходы на поддержание связей иерархии выборки.
Чтобы исключить лишние обращения к базе данных и неявное выделение памяти при обращении к ссылке, добавим получение представления прямо в текст запроса:
Запрос.Текст = "ВЫБРАТЬ
| ЗакупкаВинограда.Ссылка КАК Ссылка,
| ПРЕДСТАВЛЕНИЕ(ЗакупкаВинограда.Ссылка) КАК СсылкаПредставление
|ИЗ ...";
Теперь при выводе сообщений через Сообщить() мы будем использовать Выборка.СсылкаПредставление, что не потребует от платформы дополнительных вычислений и ресурсов.
Если вышеуказанные методы не помогают, выполним следующие шаги:
%TEMP% для кэширования результатов запросов. Убедитесь, что на диске достаточно места и у пользователя есть права на запись. Попробуйте очистить временные файлы.Тии (Тестирование и исправление) с включенными опциями проверки ссылочной целостности.Подводя итог, можно сказать: ошибка «Недостаточно памяти» в простых запросах — это чаще всего сигнал о том, что выбранный метод обхода данных (в данном случае ИТОГИ) конфликтует с внутренними механизмами оптимизации платформы или ограничениями разрядности приложения. Переход к плоским структурам данных и явное управление выборкой позволяют обойти эти ограничения и обеспечить стабильную работу системы.