Как очистить большой регистр сведений в 1С при ошибке Недостаточно памяти

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

При работе с крупными информационными базами периодически возникает задача полной очистки или усечения устаревших таблиц. Особенно часто с этим сталкиваются при накоплении технической информации: логов сторонних интеграций, протоколов обменов или технологических очередей. Если регистр сведений разрастается до сотен тысяч записей и занимает несколько гигабайт, стандартная попытка прочитать его и очистить через НаборЗаписей.Очистить() в пользовательском сеансе нередко приводит к аварийному падению платформы с ошибкой «Недостаточно памяти».

Разберем природу этой неполадки, выясним причину ее возникновения и подробно рассмотрим эффективные практические способы ее решения как средствами встроенного языка 1С, так и на уровне СУБД.

Почему возникает ошибка Недостаточно памяти

Проанализируем техническую сторону процесса, чтобы понять, из-за чего платформа исчерпывает ресурсы:

  1. Ограничение 32-разрядного клиента: Если обработка запускается в контексте обычного клиентского приложения или толстого клиента, процессу доступно не более 2–3 ГБ виртуального адресного пространства. При вызове метода Прочитать() или инициализации набора платформа пытается загрузить огромный массив строк в оперативную память клиента целиком, что моментально вызывает системный сбой.
  2. Регистрация изменений для планов обмена: Если очищаемый регистр включен в состав хотя бы одного плана обмена, стандартная операция удаления для каждой строки генерирует запись в таблице регистрации изменений. Удаление сотен тысяч записей единовременно создает колоссальный массив данных и переполняет как память, так и журнал транзакций СУБД.
  3. Обработчики событий и бизнес-логика: При стандартной записи набора вызываются подписки на события и модуль набора записей (процедуры ПередЗаписью, ПриЗаписи), что требует значительных накладных расходов.
  4. Длительные блокировки: Попытка удалить всю таблицу за одну операцию приводит к эскалации блокировок до уровня таблицы в SQL-сервере, парализуя работу остальных пользователей базы данных.

Способ 1. Запись пустого набора записей без отборов

Рассмотрим самый простой и быстрый способ очистки независимого регистра средствами встроенного языка. Распространенная ошибка разработчиков заключается в вызове метода Очистить() у предварительно прочитанного набора. Читать данные для очистки таблицы вовсе не требуется. Достаточно создать пустой объект набора записей и выполнить его безусловную запись, отключив при этом регистрацию изменений.

Разберем по шагам правильный программный код:


УстановитьПривилегированныйРежим(Истина);

Набор = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
Набор.ОбменДанными.Загрузка = Истина;
Набор.Записать(Истина);

Посмотрим на ключевые особенности этого решения:

  1. Флаг Набор.ОбменДанными.Загрузка = Истина является критически важным: он отключает вызов пользовательских обработчиков событий и блокирует регистрацию изменений в планах обмена, экономя оперативную память и снижая нагрузку на базу.
  2. Параметр Истина в методе Записать(Истина) указывает платформе, что необходимо перезаписать существующие записи пустым состоянием. Платформа трансформирует это действие в прямой SQL-запрос на удаление без предварительной загрузки строк в оперативную память.
  3. Вызов УстановитьПривилегированныйРежим(Истина) отключает RLS-ограничения, исключая построение громоздких условий проверки прав.

Способ 2. Порционная очистка через выборку и МенеджерЗаписи в транзакциях

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

Рассмотрим пример реализации циклического удаления порциями по 1 000 записей:


УстановитьПривилегированныйРежим(Истина);

РазмерПорции = 1000;
ЕстьЗаписи = Истина;

Пока ЕстьЗаписи Цикл
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0") + "
        |   Т.Период КАК Период,
        |   Т.Измерение1 КАК Измерение1
        |ИЗ
        |   РегистрСведений.ИмяВашегоРегистра КАК Т";
    
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Прервать;
    КонецЕсли;
    
    Выборка = Результат.Выбрать();
    
    НачатьТранзакцию();
    Попытка
        Пока Выборка.Следующий() Цикл
            Менеджер = РегистрыСведений.ИмяВашегоРегистра.СоздатьМенеджерЗаписи();
            Менеджер.Период = Выборка.Период;
            Менеджер.Измерение1 = Выборка.Измерение1;
            Менеджер.ОбменДанными.Загрузка = Истина;
            Менеджер.Удалить();
        КонецЦикла;
        
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение;
    КонецПопытки;
КонецЦикла;

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

Способ 3. Удаление интервалами по периоду через набор записей

Если в регистре сведений присутствует одно или фиксированное число значений измерения, но количество строк велико (например, сотни тысяч), такой регистр почти наверняка является периодическим, так как уникальность записей обеспечивается парой «Измерение + Период». В этом случае наиболее эффективным платформенным решением будет удаление записей блоками по датам (по дням, неделям или месяцам).

Посмотрим на пример удаления данных набором с отбором по интервалу дат:


УстановитьПривилегированныйРежим(Истина);

ДатаНачала = НачалоДня(ТекущаяДата()); // Указать границу очистки
ШагИнтервала = 86400 * 30; // 30 дней в секундах

Пока ДатаНачала > Дата("20150101") Цикл
    ДатаОкончания = ДатаНачала;
    ДатаНачала = ДатаОкончания - ШагИнтервала;
    
    Набор = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
    Набор.Отбор.Период.Установить(ДатаНачала, ДатаОкончания, ВидСравнения.Интервал);
    Набор.ОбменДанными.Загрузка = Истина;
    
    Попытка
        Набор.Записать(Истина);
    Исключение
        // При возникновении конфликта блокировок можно уменьшить интервал
    КонецПопытки;
КонецЦикла;

Способ 4. Прямая очистка средствами СУБД (SQL)

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

Разберем порядок действий при работе через СУБД:

  1. Определение имени физической таблицы: Имена таблиц в СУБД генерируются платформой автоматически. Для нахождения точного имени таблицы регистра выполним в 1С метод ПолучитьСтруктуруХраненияБазыДанных().
    
    Структура = ПолучитьСтруктуруХраненияБазыДанных();
    Для Каждого Стр Из Структура Цикл
        Если Стр.ИмяТаблицы1С = "РегистрСведений.ИмяВашегоРегистра" Тогда
            Сообщить("Имя таблицы в SQL: " + Стр.ИмяТаблицыХранения);
        КонецЕсли;
    КонецЦикла;
    
  2. Очистка таблицы через TRUNCATE TABLE: В среде управления СУБД (например, SQL Server Management Studio) выполним команду усечения целевой таблицы: TRUNCATE TABLE _InfoRgXXXX; Команда TRUNCATE TABLE моментально освобождает страницы данных без построчной записи в журнал транзакций (LDF), что предотвращает его разрастание.
  3. Очистка таблицы регистрации изменений: Если регистр присутствовал в планах обмена, найдем в структуре хранения соответствующую таблицу изменений (обычно имеет префикс вида _InfoRgChngXXXX) и также очистим ее, чтобы избежать появления «висячих» записей регистрации.
  4. Сброс серверного кэша 1С: После выполнения прямых манипуляций в базе данных в обход платформы обязательно перезапустим рабочие процессы сервера 1С (rphost), чтобы обновить внутренний кэш структур данных и избежать коллизий при последующих обращениях к регистру.
← На главную