При администрировании и разработке в системе «1С:Предприятие» одной из частых проблем становится неконтролируемый рост системных регистров сведений. Ярким примером является регистр ЛогДействийКассира в конфигурации «1С:Розница». Со временем в нем накапливаются миллионы записей, что приводит к значительному увеличению размера файловой базы данных (файла 1CD), замедлению работы интерфейса и трудностям при резервном копировании.
Попытка очистить такой регистр «в лоб» с помощью стандартного метода Записать(Истина) для пустого набора записей в файловой базе данных, которая к тому же работает в режиме распределенной информационной базы (РИБ), практически всегда заканчивается аварийным завершением работы программы с ошибкой «Недостаточно памяти». В этой статье мы подробно разберем причины этой проблемы и рассмотрим несколько эффективных способов её решения.
Прежде чем перейти к практическим методам решения, давайте проанализируем, почему платформа «1С:Предприятие» завершает работу аварийно при попытке очистить большой объем данных:
Самым простым системным решением для обхода ограничений памяти является запуск процедуры очистки под управлением 64-битного клиента 1С (x64). Разберем этот вариант подробнее:
В отличие от стандартного 32-битного клиента, 64-битная версия платформы не имеет жесткого ограничения на использование оперативной памяти одним процессом в 2-3 Гб. Она способна задействовать весь объем физически установленной памяти на сервере или компьютере разработчика (например, 16 Гб или 32 Гб).
Если база данных файловая, достаточно временно скопировать ее на мощный компьютер с установленной 64-битной платформой «1С:Предприятие», запустить базу в режиме «1С:Предприятие» под x64-клиентом и выполнить очистку. Транзакция удаления, требующая больших объемов памяти, успешно завершится без падения процесса. После очистки и сжатия базы ее можно вернуть на исходный рабочий компьютер.
Если запуск под 64-битным клиентом недоступен или база слишком большая, наиболее правильным программным решением является удаление данных небольшими частями (порциями) с обязательным подавлением бизнес-логики и регистрации изменений в РИБ. Рассмотрим, как реализовать этот алгоритм на практике.
Для предотвращения регистрации изменений на узлах РИБ мы должны использовать свойство ОбменДанными.Загрузка = Истина. Это критически важно, иначе процесс удаления заблокирует обмен.
Проанализируем пример кода, который выбирает данные порциями с помощью запроса и удаляет их по уникальным ключам измерений регистра:
// Процедура порционной очистки независимого регистра сведений
ВыборкаЗаписей = Новый Запрос;
ВыборкаЗаписей.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 10000"
" ЛогДействийКассира.Период КАК Период,"
" ЛогДействийКассира.Кассир КАК Кассир,"
" ЛогДействийКассира.НомерОперации КАК НомерОперации"
"ИЗ"
" РегистрСведений.ЛогДействийКассира КАК ЛогДействийКассира";
Пока Истина Цикл
РезультатЗапроса = ВыборкаЗаписей.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Прервать; // Все записи успешно удалены
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем набор записей для точечного удаления
НЗ = РегистрыСведений.ЛогДействийКассира.СоздатьНаборЗаписей();
// Устанавливаем отборы по всем измерениям регистра
НЗ.Отбор.Период.Установить(Выборка.Период);
НЗ.Отбор.Кассир.Установить(Выборка.Кассир);
НЗ.Отбор.НомерОперации.Установить(Выборка.НомерОперации);
// Важно: отключаем регистрацию в РИБ и выполнение подписок на события
НЗ.ОбменДанными.Загрузка = Истина;
// Записываем пустой набор для удаления конкретной записи
НЗ.Записать(Истина);
КонецЦикла;
// Фиксируем транзакцию и даем СУБД освободить память
ОбработкаПрерыванияПользователя();
КонецЦикла;
Обратите внимание: При использовании свойства ОбменДанными.Загрузка = Истина записи будут удалены исключительно локально (в той базе, где запущен этот код). На остальных узлах РИБ эти записи останутся нетронутыми. Чтобы обеспечить полную синхронность данных во всей распределенной сети, у вас есть два пути:
ЛогДействийКассира из состава планов обмена в конфигураторе, выполнить полную очистку на главном узле, а затем вернуть его обратно в состав плана обмена (после чего выполнить повторную инициализацию данных на узлах).В процессе разработки часто возникает соблазн использовать объект МенеджерЗаписи для удаления отдельных строк, так как его синтаксис кажется более простым и лаконичным. Проанализируем, почему для массовых операций этот подход категорически противопоказан:
МенеджерЗаписи нет встроенного свойства ОбменДанными.Загрузка. Это значит, что при каждом удалении строки через менеджер записи платформа будет принудительно запускать все зарегистрированные подписки на события и пытаться выполнить бизнес-логику, а главное — регистрировать изменения в планах обмена РИБ. Это приведет к колоссальной нагрузке на систему и переполнению оперативной памяти.МенеджерЗаписи.Удалить() под капотом выполняет больше транзакционных проверок и обращений к базе данных, чем точечная запись пустого набора записей. При массовом удалении миллионов записей разница в производительности может достигать десятков раз.НаборЗаписей позволяет гибко управлять поведением записи (например, отключать триггеры, использовать дополнительные свойства при записи), что делает его незаменимым инструментом для оптимизации массовых операций с базой данных.Ниже представлен более продвинутый универсальный алгоритм, который динамически считывает метаданные любого независимого регистра сведений, автоматически определяет его измерения и удаляет записи порциями заданной длины:
// Универсальный алгоритм для удаления записей из любого независимого регистра сведений порциями
ИмяРегистра = "ЛогДействийКассира";
РазмерПорции = 5000;
// Сформируем текст запроса динамически на основании измерений регистра
МетаданныеРегистра = Метаданные.РегистрыСведений[ИмяРегистра];
СтрокаИзмерений = "";
СтрокаВыбора = "";
Для Каждого Измерение Из МетаданныеРегистра.Измерения Цикл
ИмяИзмерения = Измерение.Имя;
СтрокаИзмерений = СтрокаИзмерений + ИмяИзмерения + ", ";
СтрокаВыбора = СтрокаВыбора + "РегистрИсточник." + ИмяИзмерения + " КАК " + ИмяИзмерения + ", ";
КонецЦикла;
// Удаляем лишнюю запятую и пробел в конце строк
СтрокаИзмерений = Лев(СтрокаИзмерений, СтрДлина(СтрокаИзмерений) - 2);
СтрокаВыбора = Лев(СтрокаВыбора, СтрДлина(СтрокаВыбора) - 2);
ТекстЗапроса = "ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПорции, "ЧГ=0") + " " + СтрокаВыбора + " ИЗ РегистрСведений." + ИмяРегистра + " КАК РегистрИсточник";
Запрос = Новый Запрос(ТекстЗапроса);
ВсегоУдалено = 0;
Пока Истина Цикл
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Прервать;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Для каждой строки создаем свой набор записей
Набор = РегистрыСведений[ИмяРегистра].СоздатьНаборЗаписей();
// Устанавливаем отборы по всем измерениям динамически
Для Каждого Измерение Из МетаданныеРегистра.Измерения Цикл
ИмяИзмерения = Измерение.Имя;
Набор.Отбор[ИмяИзмерения].Установить(Выборка[ИмяИзмерения]);
КонецЦикла;
// Отключаем обработчики и обмен данными для экономии памяти
Набор.ОбменДанными.Загрузка = Истина;
Попытка
Набор.Записать(Истина);
ВсегоУдалено = ВсегоУдалено + 1;
Исключение
Сообщить("Ошибка при удалении записи: " + ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
// Каждые N удалений выводим информацию в консоль для мониторинга
Сообщить("Успешно удалено записей: " + ВсегоУдалено);
ОбработкаПрерыванияПользователя();
КонецЦикла;
Разработчики типовой конфигурации «1С:Розница 2.2» предусмотрели штатный механизм для предотвращения бесконечного роста логов действий кассиров. Вместо написания внешних обработок и рискованных ручных манипуляций, мы рекомендуем настроить автоматическое обслуживание регистра.
Разберем по шагам, как задействовать этот механизм:
Штатное регламентное задание разработано профессионально: оно производит удаление устаревших данных небольшими порциями в фоновом режиме. Это позволяет избежать блокировок базы данных, не вызывает пиковых нагрузок на оперативную память и не приводит к падению платформы.
Бывают ситуации, когда база данных повреждена или раздулась до критического объема (например, файл 1CD превысил лимит в 4 Гб на одну таблицу), из-за чего запуск предприятия или конфигуратора становится невозможным. В этом случае программисты используют инструмент для прямого редактирования структуры СУБД 1С.
Рассмотрим последовательность действий при данном экстремальном подходе:
_InfoRgXXXX (где XXXX — порядковый номер таблицы в конкретной конфигурации). Выяснить точное имя таблицы вашего регистра можно в конфигураторе 1С с помощью функции «Получить структуру хранения базы данных» или с помощью встроенного поиска в Tool_1CD по метаданным.bin установленной платформы 1С) для исправления возможных физических ошибок индексов.Используйте этот метод только в самых крайних случаях, когда другие стандартные средства 1С оказываются бессильны.
Для решения проблемы переполнения регистра ЛогДействийКассира в РИБ мы рекомендуем придерживаться следующего плана действий. Сначала попробуйте настроить штатное регламентное задание очистки логов в «1С:Рознице». Если это невозможно из-за критического объема накопленных данных, выполните программное удаление порциями с обязательным отключением регистрации изменений (ОбменДанными.Загрузка = Истина) на каждом узле РИБ отдельно. И лишь в крайнем случае, когда платформа не дает даже запустить сеанс, прибегайте к прямому редактированию базы данных с помощью сторонних инструментов.