Как удалить записи табличной части документа в SQL с фильтром по дате?

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

В процессе эксплуатации крупных информационных баз 1С возникают ситуации, когда объем табличных частей определенных документов начинает занимать сотни гигабайт. Особенно это актуально для копий баз, предназначенных для разработки и тестирования, где полные исторические данные в строках документов не требуются, но важно сохранить сами документы и их движения (регистры). Рассмотрим, как эффективно решить задачу очистки табличных частей напрямую через СУБД MS SQL Server, сохраняя при этом целостность данных и производительность системы.

Понимание структуры таблиц 1С в SQL

Прежде чем приступать к удалению, проанализируем, как 1С хранит данные документов. Документ в SQL обычно разделен на несколько таблиц:

  1. Основная таблица (шапка): имеет имя вида _Document13383. В ней хранятся общие реквизиты: дата, номер, ссылка, проведен, пометка удаления и другие.
  2. Таблица табличной части: имеет имя вида _Document13383_VT13393. Здесь хранятся строки документа.

Связь между ними осуществляется через поле ссылки. В основной таблице это _IDRRef, а в таблице ТЧ — поле с именем _Document13383_IDRRef (имя может варьироваться в зависимости от конфигурации, но всегда содержит GUID родительского документа).

Решение 1: Прямой SQL-запрос с использованием INNER JOIN

Если нам нужно удалить строки ТЧ только за определенный период (например, до 1 мая 2022 года), мы не можем просто применить фильтр к таблице ТЧ, так как в ней нет поля «Дата». Нам необходимо «соединиться» с основной таблицей документа. Проанализируем пример рабочего запроса:


DELETE vt 
FROM _Document13383_VT13393 vt 
INNER JOIN _Document13383 t ON vt._Document13383_IDRRef = t._IDRRef 
WHERE t._Date_Time <= '40220501'

Разберем, что происходит в этом запросе:

Важно: После такого масштабного удаления рекомендуется выполнить сжатие базы данных и обновление статистики, чтобы СУБД перестроила планы запросов:

DBCC SHRINKDATABASE ('Имя_Вашей_Базы', 10);

Решение 2: Автоматизация через встроенный язык 1С

Иногда удобнее написать универсальную обработку, которая сама определит имена таблиц через метаданные и выполнит запрос. Выясним, как это реализовать, используя метод ПолучитьСтруктуруХраненияБазыДанных.


Процедура УдалитьТЧДокументовЧерезSQL(ИмяДокумента, ДатаКонца)
    ОбъектМетаданных = Метаданные.Документы[ИмяДокумента];
    МассивМД = Новый Массив;
    МассивМД.Добавить(ОбъектМетаданных);
    
    ТаблицаБД = ПолучитьСтруктуруХраненияБазыДанных(МассивМД, Истина);
    
    // Получаем имя основной таблицы
    ИмяТаблицыШапки = ТаблицаБД.Найти("Основная", "Назначение").ИмяТаблицыХранения;
    
    Для каждого СтрокаТЗ Из ТаблицаБД Цикл
        Если СтрокаТЗ.Назначение = "ТабличнаяЧасть" Тогда
            ИмяТаблицыТЧ = СтрокаТЗ.ИмяТаблицыХранения;
            ИмяПоляСсылки = "";
            
            // Ищем поле связи с шапкой
            Для каждого СтрокаПолей Из СтрокаТЗ.Поля Цикл
                Если Найти(СтрокаПолей.ИмяПоляХранения, "_IDRRef") > 0 Тогда
                    ИмяПоляСсылки = СтрокаПолей.ИмяПоляХранения;
                    Прервать;
                КонецЕсли;
            КонецЦикла;
            
            // Формируем SQL команду
            ТекстКоманды = "DELETE vt FROM " + ИмяТаблицыТЧ + " vt
                           |INNER JOIN " + ИмяТаблицыШапки + " t ON vt." + ИмяПоляСсылки + " = t._IDRRef 
                           |WHERE t._Date_Time <= '" + Формат(ДобавитьМесяц(ДатаКонца, 24000), "ДФ=yyyyMMdd") + "'";
            
            // Здесь должен быть вызов выполнения команды на уровне SQL
            ВыполнитьКомандуНаСервереSQL(ТекстКоманды);
        КонецЕсли;
    КонецЦикла; 
КонецПроцедуры

Проблема транзакционного лога (LDF) и блокировок

Проанализируем ситуацию: если в табличной части 180 миллионов строк, выполнение одного огромного запроса DELETE может привести к следующим проблемам:

  1. Переполнение диска: Лог транзакций (LDF-файл) вырастет до гигантских размеров, так как SQL должен иметь возможность откатить операцию.
  2. Эскалация блокировок: Таблица будет полностью заблокирована, и другие пользователи не смогут работать даже с теми документами, которые вы не удаляете.

Рекомендация: Удаляйте данные порциями (батчами). Рассмотрим пример цикла для SQL:


WHILE 1 = 1
BEGIN
    DELETE TOP (50000) vt 
    FROM _Document13383_VT13393 vt 
    INNER JOIN _Document13383 t ON vt._Document13383_IDRRef = t._IDRRef 
    WHERE t._Date_Time <= '40220501'
    
    IF @@ROWCOUNT = 0 BREAK
END

Такой подход позволит транзакционному логу очищаться (если база в модели Simple) и снизит нагрузку на сервер.

Кэширование на стороне 1С и целостность индексов

Посмотрим на последствия прямого вмешательства в SQL, о которых часто забывают:

Альтернативный «безопасный» метод

Если объем данных позволяет, всегда лучше использовать штатные механизмы платформы 1С. Это гарантирует очистку кэша и корректную работу индексов:


Объект = СсылкаНаДокумент.ПолучитьОбъект();
Объект.Товары.Очистить(); // Очищаем нужную ТЧ
Объект.ОбменДанными.Загрузка = Истина; // Отключаем проверки и регистрации
Объект.Записать();

Использование ОбменДанными.Загрузка = Истина значительно ускоряет процесс, так как платформа игнорирует обработчики событий и не выполняет лишних проверок. Однако для 180 миллионов строк этот метод всё равно будет медленнее, чем прямой SQL-запрос.

Выясним, какой метод выбрать: для разовой очистки огромной копии базы под разработку идеально подходит SQL DELETE с INNER JOIN порциями. Для регулярных регламентных операций в рабочей базе лучше использовать программный метод 1С.

← На главную