Почему вылетает 1С с ошибкой «Недостаточно свободной памяти» при заполнении регистра сведений и как это исправить?

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

При разработке периодических механизмов, таких как заполнение графиков работы или календарей в 1С, программисты часто сталкиваются с непредвиденным поведением системы. Одной из самых коварных ошибок является аварийное завершение работы платформы с сообщением «Недостаточно свободной памяти для выполнения операции» или сбоем HTTP-запроса к ресурсу /e1cib/logForm.

В этой статье мы подробно разберем, почему простейший алгоритм заполнения регистра сведений может за секунды переполнить оперативную память компьютера, выясним физику работы с датами в платформе «1С:Предприятие 8» и научимся писать производительный и безопасный код без лишней нагрузки на СУБД.

Анализ исходной проблемы и кода

Давайте посмотрим на исходный алгоритм, который приводит к падению системы. Разработчик пытается заполнить регистр сведений ГрафикРаботы на весь текущий год, перебирая дни в цикле:


&НаСервереБезКонтекста
Процедура ЗаполнитьГрафик(ТипГрафика)
    
    Набор = РегистрыСведений.ГрафикРаботы.СоздатьНаборЗаписей();
    Набор.Отбор.ТипГрафика.Установить(ТипГрафика);
    Набор.Записать();
    
    НачалоГод = НачалоГода(ТекущаяДата());
    КонецГод = КонецГода(ТекущаяДата());
    
    Пока НачалоГод <> КонецГод Цикл 
        Запись = Набор.Добавить();
        Запись.Дата = НачалоГод;
        Запись.ТипГрафика = ТипГрафика;
        
        ДеньНед = ДеньНедели(НачалоГод);
        Если ДеньНед <= 5 Тогда
            Запись.Значение = 8;
        КонецЕсли;
        
        НачалоГод = НачалоГод + 24*3600;
    КонецЦикла;
    
    Набор.Записать();
КонецПроцедуры

На первый взгляд, код выглядит вполне рабочим: определяется начало и конец года, в цикле к начальной дате прибавляются сутки (в секундах — 24 * 3600), формируются записи, и в конце набор записывается в базу данных. Однако при выполнении этого кода система мгновенно зависает, а затем аварийно закрывается с ошибкой нехватки ОЗУ. Выясним, почему это происходит.

Причина №1: Физика типа данных «Дата» в 1С и бесконечный цикл

Главная архитектурная ошибка кроется в строке условия цикла: Пока НачалоГод <> КонецГод Цикл. Чтобы понять, почему цикл стал бесконечным, давайте проанализируем, как платформа «1С:Предприятие» хранит даты.

В 1С тип данных Дата всегда содержит и календарную дату, и время с точностью до секунды. Если время не задано явно, оно по умолчанию инициализируется как начало суток (00:00:00).

Проследим за значениями переменных:

  1. Функция НачалоГода(ТекущаяДата()) возвращает значение 01.01.2023 00:00:00.
  2. Функция КонецГода(ТекущаяДата()) возвращает значение 31.12.2023 23:59:59. Обратите внимание на секунды!
  3. Внутри цикла к переменной НачалоГод прибавляются ровно сутки: НачалоГод = НачалоГод + 24*3600;. Это значит, что дата смещается на следующий день, но время всегда остается равным 00:00:00.
  4. В конце декабря значение переменной НачалоГод примет значение 31.12.2023 00:00:00.
  5. На следующей итерации цикла к этой дате прибавится еще 24 часа, и значение станет равным 01.01.2024 00:00:00.

Поскольку сравнение в цикле идет по строгому условию «не равно» (<>), система сравнивает 31.12.2023 00:00:00 с 31.12.2023 23:59:59. Эти значения не равны. На следующем шаге сравнивается 01.01.2024 00:00:00 с 31.12.2023 23:59:59 — они снова не равны. Условие цикла никогда не вернет ложь, и цикл уходит в бесконечность, продолжая прибавлять дни и годы дальше.

Причина №2: Переполнение памяти объектом «НаборЗаписей»

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

При работе с объектом РегистрСведенийНаборЗаписей метод Набор.Добавить() не производит мгновенную запись в базу данных СУБД. Все добавляемые строки накапливаются в оперативной памяти (ОЗУ) текущего рабочего процесса. В бесконечном цикле в этот массив на сервере (или на клиенте в случае файловой базы) каждую секунду добавляются тысячи новых строк.

Рано или поздно размер набора данных превышает лимиты адресного пространства процесса 1С (для 32-битных систем это около 2-4 ГБ, но даже на 64-битных системах физическая память компьютера быстро исчерпывается). Рабочий процесс сервера 1С rphost или клиентское приложение аварийно завершается операционной системой. Из-за разрыва соединения клиент получает ошибку веб-транспорта при обращении к форме (/e1cib/logForm).

Решение проблемы: правильное сравнение дат

Для устранения бесконечного цикла достаточно изменить условие завершения. Самое простое и быстрое исправление — заменить строгое неравенство на оператор «меньше или равно» (<=):


Пока НачалоГод <= КонецГод Цикл 
    // логика заполнения
КонецЦикла;

Когда значение переменной НачалоГод перешагнет через границу и станет равным 01.01.2024 00:00:00, система сравнит его с 31.12.2023 23:59:59. Так как первая дата больше второй, цикл корректно завершит свою работу.

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

Профессиональное решение и оптимизация кода

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

Важная деталь оптимизации: Обратите внимание на строки в исходном коде автора:


Набор = РегистрыСведений.ГрафикРаботы.СоздатьНаборЗаписей();
Набор.Отбор.ТипГрафика.Установить(ТипГрафика);
Набор.Записать(); // <-- Лишнее обращение к базе данных!
...
Набор.Записать(); // <-- Повторная запись

Первый вызов метода Набор.Записать() без добавления строк очищает регистр по выбранному типу графика. Однако объект НаборЗаписей в 1С при записи всегда полностью заменяет существующие в базе данных записи, соответствующие установленному отбору. Это значит, что первая «очищающая» запись абсолютно не нужна. Она лишь создает лишнюю транзакцию, нагружая СУБД и блокируя таблицы. Достаточно вызвать метод Записать() всего один раз — в самом конце, после заполнения набора.

Применим правильное приведение дат к началу дня с помощью функции НачалоДня(). Это гарантирует, что время обеих границ периода всегда будет равно 00:00:00, что исключает любые ошибки округления или сравнения.

Посмотрим на оптимизированный вариант серверной процедуры:


&НаСервереБезКонтекста
Процедура ЗаполнитьГрафик(ТипГрафика)
    
    // 1. Создаем набор записей и устанавливаем отбор
    Набор = РегистрыСведений.GradikRaboty.СоздатьНаборЗаписей();
    Набор.Отбор.ТипГрафика.Установить(ТипГрафика);
    
    // Метод Набор.Записать() здесь НЕ вызываем, чтобы не делать лишних транзакций!
    
    // 2. Нормализуем границы периодов к началу дня (время 00:00:00)
    ТекущаяДатаСеанса = ТекущаяДатаСеанса();
    ДатаНачала = НачалоГода(ТекущаяДатаСеанса);
    ДатаОкончания = НачалоДня(КонецГода(ТекущаяДатаСеанса));
    
    ТекущаяДатаОбработки = ДатаНачала;
    
    // 3. Запускаем безопасный цикл по дням
    Пока ТекущаяДатаОбработки <= ДатаОкончания Цикл 
        
        НоваяЗапись = Набор.Добавить();
        НоваяЗапись.Дата = ТекущаяДатаОбработки;
        НоваяЗапись.ТипГрафика = ТипГрафика;
        
        // Определяем день недели для заполнения рабочих часов
        ДеньНедели = ДеньНедели(ТекущаяДатаОбработки);
        Если ДеньНедели <= 5 Тогда
            НоваяЗапись.Значение = 8; // Пятидневка, 8-часовой рабочий день
        Иначе
            НоваяЗапись.Значение = 0; // Выходные дни
        КонецЕсли;
        
        // Шаг цикла — ровно 1 день (86 400 секунд)
        ТекущаяДатаОбработки = ТекущаяДатаОбработки + 86400; 
        
    КонецЦикла;
    
    // 4. Записываем весь набор за одну транзакцию. 
    // Существующие в базе записи по этому ТипуГрафика будут автоматически удалены.
    Попытка
        Набор.Записать(Истина); // Параметр Истина указывает на замещение записей
    Исключение
        ВызватьИсключение "Не удалось записать график работы в базу данных: " + ОписаниеОшибки();
    КонецПопытки;
    
КонецПроцедуры

Памятка разработчику: правила работы с циклами по датам

Чтобы избежать подобных критических ошибок в будущем, всегда придерживайтесь следующих правил при написании кода в 1С:

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

← На главную