В процессе сопровождения информационных систем на базе 1С программисты часто сталкиваются с задачей очистки данных по определенному критерию. Одной из самых трудоемких операций является удаление записей из регистра накопления, если критерием выступает не документ-регистратор, а значение измерения (например, Организация, Склад или Номенклатура). Ситуация осложняется, когда объем записей исчисляется миллионами, а стандартное распроведение документов занимает слишком много времени.
В этой статье мы подробно разберем, почему платформа 1С накладывает определенные ограничения на работу с регистрами накопления, и проанализируем наиболее эффективные методы удаления данных, включая программные способы и оптимизацию производительности.
Для начала проанализируем архитектуру регистров накопления. В отличие от регистров сведений, регистры накопления всегда подчинены регистратору. Это означает, что каждая запись в таблице базы данных жестко связана с документом, который ее породил. Платформа 1С:Предприятие спроектирована таким образом, что запись в регистр возможна только через объект РегистрНакопленияНаборЗаписей, для которого в обязательном порядке должен быть установлен отбор по полю Регистратор.
Рассмотрим типичную ошибку начинающего разработчика. Попытка записать пустой набор с отбором только по измерению (например, по организации) приведет к системному исключению. Выясним причину: объект НаборЗаписей требует уникальной идентификации контекста записи, которой в данном случае выступает ссылка на документ. Без указания регистратора система не "понимает", какие именно записи в физической таблице БД нужно модифицировать или удалить.
Разберем стандартный и наиболее безопасный алгоритм решения задачи. Поскольку мы не можем удалить данные напрямую по измерению, нам необходимо сначала найти все документы, которые создали движения по нужной нам организации, а затем программно очистить их движения.
Шаг 1. Получение списка регистраторов. Нам необходимо составить список всех уникальных ссылок на документы. Для этого лучше всего использовать запрос к основной таблице регистра. Проанализируем пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| МойРегистр.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.МойРегистр КАК МойРегистр
|ГДЕ
| МойРегистр.Организация = &Организация";
Запрос.УстановитьПараметр("Организация", ВыбраннаяОрганизация);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Использование ключевого слова РАЗЛИЧНЫЕ критически важно, так как один документ может порождать десятки и сотни строк в регистре, а нам нужна только одна ссылка на него для создания набора записей.
Шаг 2. Программная очистка движений. После получения списка документов мы переходим к циклическому обходу. Для каждого регистратора мы создаем пустой набор записей и записываем его поверх существующих данных. Посмотрим на реализацию:
Пока Выборка.Следующий() Цикл
Набор = РегистрыНакопления.МойРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Выборка.Регистратор);
// Важный момент: мы не добавляем строки в набор, оставляя его пустым
Набор.ОбменДанными.Загрузка = Истина;
Набор.Записать(Истина); // Параметр Истина означает замещение существующих записей
КонецЦикла;
Метод Записать(Истина) при пустом наборе фактически удаляет все записи данного документа в этом регистре. Это работает значительно быстрее, чем удаление через объект документа или его распроведение, так как система не пересчитывает другие связанные данные и не выполняет тяжелые проверки бизнес-логики.
Если в регистре миллионы записей, обычный цикл может выполняться часами. Проанализируем ситуацию: основное время тратится не на само удаление строк из таблицы, а на обновление итогов регистра (таблицы текущих остатков и итогов по периодам). Рассмотрим способы ускорения процесса.
1. Использование свойства ОбменДанными.Загрузка. Установка флага ОбменДанными.Загрузка = Истина сообщает платформе, что запись производится в режиме загрузки данных. Это отключает выполнение подписок на события (ПередЗаписью, ПриЗаписи) и некоторые внутренние проверки платформы, что дает прирост скорости в 2-3 раза.
2. Временное отключение итогов. Это самый мощный инструмент оптимизации. Перед началом массового удаления мы можем полностью отключить расчет итогов для конкретного регистра. В этом случае система перестанет при каждой записи пересчитывать агрегатные данные.
// Отключаем итоги
РегистрыНакопления.МойРегистр.УстановитьИспользованиеИтогов(Ложь);
// Выполняем цикл удаления (описанный выше)
// ...
// Включаем итоги обратно и пересчитываем их один раз
РегистрыНакопления.МойРегистр.УстановитьИспользованиеИтогов(Истина);
Важно помнить, что включение итогов после массового удаления запустит их полный пересчет. Однако один итоговый пересчет всегда быстрее, чем миллион промежуточных обновлений при каждой записи набора.
3. Пакетная обработка транзакций. При удалении очень большого объема данных (например, по 500 000 регистраторов) лог транзакций SQL-сервера может переполниться. Чтобы избежать этого, рекомендуется фиксировать транзакцию порциями, например, каждые 1000 документов. Рассмотрим, как это реализовать:
Счетчик = 0;
Пока Выборка.Следующий() Цикл
Если Счетчик % 1000 = 0 Тогда
Если Счетчик > 0 Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
НачатьТранзакцию();
КонецЕсли;
Набор = РегистрыНакопления.МойРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Выборка.Регистратор);
Набор.ОбменДанными.Загрузка = Истина;
Набор.Записать(Истина);
Счетчик = Счетчик + 1;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
В исключительных случаях, когда штатные средства 1С не справляются (например, база повреждена или время простоя критично), программисты обращаются к прямому редактированию таблиц на уровне СУБД (MS SQL или PostgreSQL). Выясним, как это устроено.
Данные регистра накопления хранятся в таблице с именем вида _AccumRegX. Чтобы найти нужное имя таблицы, можно воспользоваться функцией ПолучитьСтруктуруХраненияБазыДанных(). Прямой SQL-запрос DELETE FROM _AccumRegX WHERE _FldY = 'ID_Организации' выполнится практически мгновенно.
Однако этот метод несет огромные риски:
_AccumRegTotalsX). После SQL-удаления итоги будут показывать неверные данные.Мы настоятельно рекомендуем использовать SQL-метод только в крайних случаях и только после создания полной резервной копии базы данных.
Мы выяснили, что удаление записей регистра накопления по конкретному измерению требует обходного пути через регистраторы документов. Подведем краткий итог правильной последовательности действий:
Загрузка = Истина.Записать(Истина).Соблюдение этих правил позволит вам быстро и безопасно очистить даже самые объемные регистры накопления в системе 1С:Предприятие.