При работе с крупными информационными базами периодически возникает задача полной очистки или усечения устаревших таблиц. Особенно часто с этим сталкиваются при накоплении технической информации: логов сторонних интеграций, протоколов обменов или технологических очередей. Если регистр сведений разрастается до сотен тысяч записей и занимает несколько гигабайт, стандартная попытка прочитать его и очистить через НаборЗаписей.Очистить() в пользовательском сеансе нередко приводит к аварийному падению платформы с ошибкой «Недостаточно памяти».
Разберем природу этой неполадки, выясним причину ее возникновения и подробно рассмотрим эффективные практические способы ее решения как средствами встроенного языка 1С, так и на уровне СУБД.
Проанализируем техническую сторону процесса, чтобы понять, из-за чего платформа исчерпывает ресурсы:
Прочитать() или инициализации набора платформа пытается загрузить огромный массив строк в оперативную память клиента целиком, что моментально вызывает системный сбой.ПередЗаписью, ПриЗаписи), что требует значительных накладных расходов.Рассмотрим самый простой и быстрый способ очистки независимого регистра средствами встроенного языка. Распространенная ошибка разработчиков заключается в вызове метода Очистить() у предварительно прочитанного набора. Читать данные для очистки таблицы вовсе не требуется. Достаточно создать пустой объект набора записей и выполнить его безусловную запись, отключив при этом регистрацию изменений.
Разберем по шагам правильный программный код:
УстановитьПривилегированныйРежим(Истина);
Набор = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
Набор.ОбменДанными.Загрузка = Истина;
Набор.Записать(Истина);
Посмотрим на ключевые особенности этого решения:
Набор.ОбменДанными.Загрузка = Истина является критически важным: он отключает вызов пользовательских обработчиков событий и блокирует регистрацию изменений в планах обмена, экономя оперативную память и снижая нагрузку на базу.Истина в методе Записать(Истина) указывает платформе, что необходимо перезаписать существующие записи пустым состоянием. Платформа трансформирует это действие в прямой SQL-запрос на удаление без предварительной загрузки строк в оперативную память.УстановитьПривилегированныйРежим(Истина) отключает RLS-ограничения, исключая построение громоздких условий проверки прав.Если регистр настолько велик, что удаление всего объема за раз вызывает тайм-аут блокировки или блокирует активную работу пользователей в рабочей базе, очистку необходимо выполнять небольшими порциями. Для этого организуют цикл выборки с ограничением объема одной транзакции.
Рассмотрим пример реализации циклического удаления порциями по 1 000 записей:
УстановитьПривилегированныйРежим(Истина);
РазмерПорции = 1000;
ЕстьЗаписи = Истина;
Пока ЕстьЗаписи Цикл
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0") + "
| Т.Период КАК Период,
| Т.Измерение1 КАК Измерение1
|ИЗ
| РегистрСведений.ИмяВашегоРегистра КАК Т";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Прервать;
КонецЕсли;
Выборка = Результат.Выбрать();
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
Менеджер = РегистрыСведений.ИмяВашегоРегистра.СоздатьМенеджерЗаписи();
Менеджер.Период = Выборка.Период;
Менеджер.Измерение1 = Выборка.Измерение1;
Менеджер.ОбменДанными.Загрузка = Истина;
Менеджер.Удалить();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецЦикла;
В этом сценарии память освобождается после каждой итерации, а транзакции небольшого размера не блокируют параллельные сеансы пользователей.
Если в регистре сведений присутствует одно или фиксированное число значений измерения, но количество строк велико (например, сотни тысяч), такой регистр почти наверняка является периодическим, так как уникальность записей обеспечивается парой «Измерение + Период». В этом случае наиболее эффективным платформенным решением будет удаление записей блоками по датам (по дням, неделям или месяцам).
Посмотрим на пример удаления данных набором с отбором по интервалу дат:
УстановитьПривилегированныйРежим(Истина);
ДатаНачала = НачалоДня(ТекущаяДата()); // Указать границу очистки
ШагИнтервала = 86400 * 30; // 30 дней в секундах
Пока ДатаНачала > Дата("20150101") Цикл
ДатаОкончания = ДатаНачала;
ДатаНачала = ДатаОкончания - ШагИнтервала;
Набор = РегистрыСведений.ИмяВашегоРегистра.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(ДатаНачала, ДатаОкончания, ВидСравнения.Интервал);
Набор.ОбменДанными.Загрузка = Истина;
Попытка
Набор.Записать(Истина);
Исключение
// При возникновении конфликта блокировок можно уменьшить интервал
КонецПопытки;
КонецЦикла;
В критических ситуациях, когда регистр полностью заброшен, не связан с актуальной логикой и исчисляется десятками гигабайт, быстрее и надежнее всего применить административные средства SQL-сервера. Этот способ требует монопольного доступа к таблице или проведения регламентного окна.
Разберем порядок действий при работе через СУБД:
ПолучитьСтруктуруХраненияБазыДанных().
Структура = ПолучитьСтруктуруХраненияБазыДанных();
Для Каждого Стр Из Структура Цикл
Если Стр.ИмяТаблицы1С = "РегистрСведений.ИмяВашегоРегистра" Тогда
Сообщить("Имя таблицы в SQL: " + Стр.ИмяТаблицыХранения);
КонецЕсли;
КонецЦикла;
TRUNCATE TABLE _InfoRgXXXX;
Команда TRUNCATE TABLE моментально освобождает страницы данных без построчной записи в журнал транзакций (LDF), что предотвращает его разрастание._InfoRgChngXXXX) и также очистим ее, чтобы избежать появления «висячих» записей регистрации.