При разработке высоконагруженных систем на платформе 1С:Предприятие часто возникает задача массовой очистки или выборочного удаления устаревших данных из независимых регистров сведений. Если регистр насчитывает миллионы записей, неверно выбранный способ удаления может привести к полной блокировке таблицы на стороне СУБД, взаимоблокировкам (deadlocks) и замедлению параллельной работы пользователей и регламентных заданий.
Разберем основные архитектурные ограничения платформы, влияние операций на СУБД и оптимальные паттерны пакетного удаления данных.
Проанализируем базовый механизм работы объекта РегистрСведенийНаборЗаписей. В платформе 1С нельзя установить отбор набора записей по произвольному неиндексированному измерению или реквизиту. Коллекция Отбор набора записей независимого регистра позволяет накладывать фильтры исключительно на те измерения, которые:
Если попытаться обойти платформенный механизм через прямой запрос на уровне СУБД с условием по неиндексированному полю, СУБД выполнит полное сканирование таблицы (Clustered Index Scan / Full Scan). В момент удаления это заблокирует огромные диапазоны строк или приведет к эскалации блокировки до уровня всей таблицы.
Выясним причину, почему удаление даже нескольких тысяч строк единым набором может парализовать базу данных. В СУБД (например, MS SQL Server) действует механизм эскалации блокировок (Lock Escalation). При модификации более 5 000 записей (или удержании более 5 000 блокировок) в рамках одной транзакции СУБД автоматически заменяет точечные блокировки строк на блокировку всей физической таблицы.
В результате все параллельные фоновые задания, пытающиеся выполнить запись в этот регистр, будут ожидать снятия блокировки таблицы вплоть до превышения таймаута (Lock Timeout).
Рассмотрим стабильный и безопасный подход к удалению записей, который разбивает нагрузку на контролируемые порции и предотвращает блокировку всей таблицы.
Разберем алгоритм по шагам:
РегистрСведенийНаборЗаписей или РегистрСведенийМенеджерЗаписи с полным отбором по ключевым полям.Посмотрим на пример реализации пакетного удаления:
РазмерПакета = 1000;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ " + Формат(РазмерПакета, "ЧГ=0") + "
| Регистр.Организация КАК Организация,
| Регистр.Номенклатура КАК Номенклатура,
| Регистр.ПериодДействия КАК ПериодДействия
|ИЗ
| РегистрСведений.КурсыВалют КАК Регистр
|ГДЕ
| Регистр.ПериодДействия < &ГраницаОчистки";
Запрос.УстановитьПараметр("ГраницаОчистки", ГраницаУдаления);
Пока Истина Цикл
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Количество() = 0 Тогда
Прервать;
КонецЕсли;
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Организация.Установить(Выборка.Организация);
НаборЗаписей.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НаборЗаписей.Отбор.ПериодДействия.Установить(Выборка.ПериодДействия);
НаборЗаписей.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецЦикла;
Рассмотрим, как правильно настроить метаданные, если регистр заведомо проектируется под регулярную очистку по определенному измерению:
INSERT) и увеличивает объем файла базы данных. На регистрах с миллионными суточными оборотами не стоит включать индексирование на всех полях без явной необходимости.Если стоит задача не выборочного удаления, а полной очистки таблицы, не следует удалять данные построчно. В платформе 1С для этого предусмотрен метод менеджера регистра Очистить():
РегистрыСведений.КурсыВалют.Очистить();
На уровне СУБД данный вызов транслируется в быструю операцию TRUNCATE TABLE, которая мгновенно освобождает страницы данных без раздувания журнала транзакций и без блокировок отдельных строк.