Как правильно удалить записи регистра сведений напрямую через SQL и избежать ошибок в 1С?

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

В процессе эксплуатации высоконагруженных систем 1С часто возникают ситуации, когда стандартные методы платформы по удалению записей из регистров сведений работают критически медленно. Особенно это заметно, когда регистр подчинен регистратору и содержит сотни тысяч или миллионы строк. Прямое использование SQL-запросов позволяет значительно ускорить процесс, однако такой подход несет в себе скрытые риски для логической целостности базы данных. В этой статье мы подробно разберем, как реализовать удаление через SQL, сохраняя работоспособность системы, и на какие «подводные камни» стоит обратить внимание.

Почему штатные методы могут не подходить?

Рассмотрим стандартную ситуацию: нам необходимо выборочно удалить записи из регистра сведений, подчиненного регистратору. Типичный код на языке 1С выглядит следующим образом:


НаборЗаписей = РегистрыСведений.ВашРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
НаборЗаписей.Прочитать();
// Далее идет цикл по записям и удаление лишних
НаборЗаписей.Записать();

Проанализируем причину низкой производительности. Метод Прочитать() выгружает все записи по конкретному регистратору в память сервера приложений. Если у одного документа 100 000 записей, а нам нужно удалить всего 50, система все равно выполнит тяжелую операцию чтения и последующую полную перезапись набора. В таких случаях прямой SQL-запрос DELETE может отработать за миллисекунды, в то время как штатный метод займет минуты.

Оптимизация без прямого SQL

Прежде чем переходить к опасным операциям в СУБД, разберем промежуточный вариант. Если мы точно знаем, какие записи должны остаться, мы можем использовать запись набора «поверх» существующих без предварительного чтения. Это сэкономит время на выгрузке данных в память:


НаборЗаписей = РегистрыСведений.ВашРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
// Мы НЕ вызываем Прочитать()
// Сразу добавляем в НаборЗаписей только те строки, которые ДОЛЖНЫ ОСТАТЬСЯ
НоваяЗапись = НаборЗаписей.Добавить();
// Заполнение полей...
НаборЗаписей.Записать(Истина); // Флаг Истина полностью замещает старые записи новыми

Однако, если объем оставляемых данных все равно велик, этот метод тоже будет медленным из-за транзакционной логики платформы. Здесь на сцену выходит прямой SQL.

Прямое удаление через SQL: базовый синтаксис и смещение дат

Для удаления записей напрямую нам необходимо определить имя таблицы в базе данных SQL. Это можно сделать с помощью метода ПолучитьСтруктуруХраненияБазыДанных(). Выясним основные правила формирования запроса. Важно помнить, что 1С хранит даты со смещением 2000 лет. Если вы фильтруете данные по периоду, учитывайте это.

Простейший запрос на удаление выглядит так:


DELETE FROM _InfoRg1234 -- Имя таблицы вашего регистра
WHERE _RecorderRRef = 0x80DB005056916B1B11E9 -- UID регистратора в бинарном формате
AND _Period < '4023-01-01' -- Дата 2023 год со смещением +2000

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

Подводный камень №1: Нумерация строк (LineNo)

В регистрах сведений, подчиненных регистратору, существует поле _LineNo (Номер строки). Когда вы удаляете записи выборочно через SQL, в нумерации оставшихся строк образуются «дырки». Платформа 1С ожидает, что номера строк в наборе идут строго по порядку (1, 2, 3...). Нарушение этой последовательности может привести к ошибкам при последующей попытке штатного редактирования документа пользователем.

Рассмотрим, как восстановить нумерацию строк после удаления. Для этого эффективно использовать оконную функцию ROW_NUMBER() в SQL Server. Проанализируем пример запроса, который пересчитывает номера строк для конкретных регистраторов:


UPDATE T
SET T._LineNo = TT.NewLineNo
FROM _InfoRg1234 AS T
INNER JOIN (
    SELECT 
        _RecorderTRef, 
        _RecorderRRef, 
        _LineNo,
        ROW_NUMBER() OVER (
            PARTITION BY _RecorderTRef, _RecorderRRef 
            ORDER BY _LineNo
        ) AS NewLineNo
    FROM _InfoRg1234
    WHERE _RecorderRRef IN (SELECT ... список измененных документов ...)
) AS TT ON T._RecorderTRef = TT._RecorderTRef 
      AND T._RecorderRRef = TT._RecorderRRef 
      AND T._LineNo = TT._LineNo
WHERE T._LineNo <> TT.NewLineNo;

Этот подход позволяет быстро «схлопнуть» нумерацию, сделав ее валидной для платформы 1С.

Подводный камень №2: Таблицы актуальных записей (Срезы)

Для периодических регистров сведений 1С часто создает дополнительные таблицы с суффиксом _Actuals (например, _InfoRg1234_Actuals). Они используются для мгновенного получения «Среза последних». Если вы удалили запись из основной таблицы, но не почистили таблицу актуальных записей, возникнет десинхронизация: запрос среза последних в 1С будет возвращать старое (удаленное) значение, хотя в детальных записях его уже нет.

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

  1. Обновить соответствующие записи в таблице _Actuals через SQL.
  2. Запустить в 1С процедуру Тестирование и исправление с установленным флагом «Пересчет итогов».

Подводный камень №3: Планы обмена и кэширование

Если регистр участвует в обмене данными (зарегистрирован в Плане обмена), то прямое удаление в SQL не создаст записей регистрации изменений. Это значит, что в другие базы (например, из УТ в Бухгалтерию) информация об удалении не попадет. Данные там останутся «висеть», что приведет к расхождениям в учете.

Также стоит учитывать серверный кэш. Сервер приложений 1С кэширует записи регистров. После выполнения SQL-запроса пользователи могут видеть старые данные в формах списков. Для решения этой проблемы может потребоваться перезапуск рабочих процессов сервера или выполнение метода ОбновитьПовторноИспользуемыеЗначения().

Подводный камень №4: Блокировки и лог транзакций

При выполнении команды DELETE на огромных объемах данных (миллионы строк) SQL Server может применить Lock Escalation (эскалацию блокировок). Вместо того чтобы блокировать конкретные строки, сервер заблокирует всю таблицу целиком. В это время ни один пользователь не сможет даже прочитать данные из этого регистра.

Разберем рекомендации по выполнению массовых операций:

Подводный камень №5: Флаги в таблицах документов

У каждого документа-регистратора в его основной таблице (например, _Document123) могут быть скрытые поля-флаги, указывающие на наличие движений по конкретным регистрам. Если вы полностью удаляете все движения документа через SQL, платформа может продолжать считать, что движения есть, так как флаг в таблице документа не был сброшен. Это может привести к ошибкам при попытке удаления самого документа-регистратора стандартными средствами 1С.

Заключение и рекомендации

Прямая работа с SQL — это мощный инструмент, который стоит использовать только тогда, когда штатные методы НаборЗаписей действительно не справляются. Проанализировав все риски, мы можем составить безопасный алгоритм действий:

  1. Создайте резервную копию базы данных. Это обязательный шаг перед любым вмешательством в структуру SQL.
  2. Определите точный список имен таблиц (основная, актуальные, таблицы итогов).
  3. Выполните удаление записей, учитывая смещение дат (+2000 лет).
  4. Запустите скрипт восстановления нумерации _LineNo.
  5. Если регистр периодический — выполните пересчет итогов через ТиИ.
  6. Очистите серверный кэш 1С.

Помните, что во многих случаях вместо DELETE выгоднее использовать TRUNCATE (если нужно очистить регистр полностью) или схему с переливом нужных данных в промежуточную таблицу с последующей подменой (RENAME), так как INSERT в SQL обычно работает быстрее, чем массовый DELETE.

← На главную