При работе с большими базами данных в 1С 7.7 с использованием прямых SQL-запросов (компоненты 1С++ или Rainbow) разработчики часто сталкиваются с критической ошибкой «нехватка оперативной памяти». Эта проблема особенно актуальна при попытке построить отчеты по остаткам и оборотам в разрезе каждого дня за длительный период. Рассмотрим причины возникновения этой ошибки и разберем эффективные способы оптимизации тяжелых запросов.
Прежде всего, важно понимать, что 1cv7.exe является 32-битным приложением. Это означает, что операционная система выделяет ему ограниченный объем адресного пространства (обычно до 2 Гб). Когда выполняется прямой SQL-запрос, результат которого содержит миллионы строк, компонента 1С++ пытается загрузить весь этот объем данных (Recordset) в память клиента. Если данных слишком много, память переполняется, и приложение аварийно завершается.
Проанализируем основные «узкие места» в представленном коде, которые гарантированно приводят к краху системы при больших объемах номенклатуры:
UPDATE ... WHERE CURRENT OF cur — это самый ресурсозатратный способ расчета накопленного итога в SQL Server.Вместо того чтобы создавать пустую «сетку» для каждого товара на каждый день, проанализируем ситуацию: нам нужны данные только по тем дням, когда были движения, плюс начальный остаток. Дни, в которых не было движений, можно не хранить в оперативной памяти SQL, а обрабатывать логически. Если же бизнес-логика требует наличия всех дат (например, для расчета среднего остатка), лучше выполнять агрегацию сразу в SQL.
Рассмотрим, как можно изменить структуру получения данных. Вместо создания таблицы #Период и ее перемножения, мы можем использовать левое соединение к реальным движениям или вообще исключить пустые даты на этапе запроса.
Использование курсоров в SQL — это «антипаттерн» для высокопроизводительных систем. Для расчета ежедневных остатков (Running Total) в современных версиях MS SQL Server (2012+) идеально подходят оконные функции. Если же используется старая версия SQL (что часто бывает в связке с 7.7), можно применить метод накопления через переменные в одном UPDATE.
Посмотрим на пример логики расчета без курсора с использованием оконной функции SUM() OVER():
SELECT
Товар,
Дата,
Оборот,
SUM(Оборот) OVER (PARTITION BY Товар ORDER BY Дата ROWS UNBOUNDED PRECEDING) + НачальныйОстаток AS ТекущийОстаток
FROM #Движения
Этот подход работает в десятки раз быстрее курсора и потребляет значительно меньше памяти сервера.
Выясним причину, по которой автору требуются ежедневные цифры. Если целью является получение «свернутых» показателей (средний остаток, сумма поступлений), то нет никакого смысла возвращать в 1С миллионы строк. Проанализируем, как можно вычислить средний остаток математически прямо в запросе.
Среднедневной остаток можно рассчитать как сумму остатков на конец каждого дня, деленную на количество дней. Но еще эффективнее использовать формулу взвешенного среднего: (Остаток * КоличествоДнейДоСледующегоДвижения) / ОбщееКоличествоДней.
Пример структуры запроса, который вернет в 1С только итоговые цифры по товару:
SELECT
Итоги.Товар as [Номенклатура $Справочник.Номенклатура],
SUM(Итоги.Поступление) as [ВсегоПоступлений],
SUM(Итоги.Остаток) / :КоличествоДней as [СреднийОстаток]
FROM #Итоги as Итоги
GROUP BY Итоги.Товар
Такой результат будет содержать всего 10 000 строк вместо 3.6 миллионов, что легко поместится в память 1С.
В исходном запросе используется $РегистрОстатки.ОстаткиТМЦ. Внутри 1С++ это разворачивается в сложный подзапрос. При работе с фильтрами по #TempTable (список номенклатуры) старайтесь избегать конструкции IN (SELECT val FROM #TempTable) внутри параметров виртуальной таблицы. Проанализируем более производительный вариант: сначала получить все остатки, а затем сделать JOIN с вашей временной таблицей.
Также обратите внимание на индексацию. В исходном коде индекс CREATE INDEX DateItem ON #Итоги (Товар,Дата) создается до вставки данных. При большом объеме INSERT будет работать крайне медленно, так как SQL придется перестраивать индекс после каждой строки. Правильнее сначала вставить данные, а затем создать индекс.
Товары * Дни > 500 000, никогда не возвращайте детальную таблицу в 1С.GROUP BY по товару в финальном SELECT.DROP TABLE сразу после того, как таблица перестала быть нужной, чтобы не переполнять базу tempdb на сервере.Соблюдение этих правил позволит обрабатывать даже самые тяжелые отчеты без риска падения системы и обеспечит высокую скорость работы пользователей в 1С 7.7.