В процессе эксплуатации высоконагруженных систем 1С часто возникают ситуации, когда стандартные методы платформы по удалению записей из регистров сведений работают критически медленно. Особенно это заметно, когда регистр подчинен регистратору и содержит сотни тысяч или миллионы строк. Прямое использование SQL-запросов позволяет значительно ускорить процесс, однако такой подход несет в себе скрытые риски для логической целостности базы данных. В этой статье мы подробно разберем, как реализовать удаление через SQL, сохраняя работоспособность системы, и на какие «подводные камни» стоит обратить внимание.
Рассмотрим стандартную ситуацию: нам необходимо выборочно удалить записи из регистра сведений, подчиненного регистратору. Типичный код на языке 1С выглядит следующим образом:
НаборЗаписей = РегистрыСведений.ВашРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
НаборЗаписей.Прочитать();
// Далее идет цикл по записям и удаление лишних
НаборЗаписей.Записать();
Проанализируем причину низкой производительности. Метод Прочитать() выгружает все записи по конкретному регистратору в память сервера приложений. Если у одного документа 100 000 записей, а нам нужно удалить всего 50, система все равно выполнит тяжелую операцию чтения и последующую полную перезапись набора. В таких случаях прямой SQL-запрос DELETE может отработать за миллисекунды, в то время как штатный метод займет минуты.
Прежде чем переходить к опасным операциям в СУБД, разберем промежуточный вариант. Если мы точно знаем, какие записи должны остаться, мы можем использовать запись набора «поверх» существующих без предварительного чтения. Это сэкономит время на выгрузке данных в память:
НаборЗаписей = РегистрыСведений.ВашРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(ДокументСсылка);
// Мы НЕ вызываем Прочитать()
// Сразу добавляем в НаборЗаписей только те строки, которые ДОЛЖНЫ ОСТАТЬСЯ
НоваяЗапись = НаборЗаписей.Добавить();
// Заполнение полей...
НаборЗаписей.Записать(Истина); // Флаг Истина полностью замещает старые записи новыми
Однако, если объем оставляемых данных все равно велик, этот метод тоже будет медленным из-за транзакционной логики платформы. Здесь на сцену выходит прямой SQL.
Для удаления записей напрямую нам необходимо определить имя таблицы в базе данных SQL. Это можно сделать с помощью метода ПолучитьСтруктуруХраненияБазыДанных(). Выясним основные правила формирования запроса. Важно помнить, что 1С хранит даты со смещением 2000 лет. Если вы фильтруете данные по периоду, учитывайте это.
Простейший запрос на удаление выглядит так:
DELETE FROM _InfoRg1234 -- Имя таблицы вашего регистра
WHERE _RecorderRRef = 0x80DB005056916B1B11E9 -- UID регистратора в бинарном формате
AND _Period < '4023-01-01' -- Дата 2023 год со смещением +2000
Важно: Прямое изменение данных в обход платформы нарушает лицензионное соглашение и может привести к непредсказуемым последствиям, если не учитывать внутренние механизмы 1С.
В регистрах сведений, подчиненных регистратору, существует поле _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С.
Для периодических регистров сведений 1С часто создает дополнительные таблицы с суффиксом _Actuals (например, _InfoRg1234_Actuals). Они используются для мгновенного получения «Среза последних». Если вы удалили запись из основной таблицы, но не почистили таблицу актуальных записей, возникнет десинхронизация: запрос среза последних в 1С будет возвращать старое (удаленное) значение, хотя в детальных записях его уже нет.
Проанализируем ситуацию: если ваш регистр периодический, после массового SQL-удаления обязательно нужно выполнить одно из действий:
_Actuals через SQL.Если регистр участвует в обмене данными (зарегистрирован в Плане обмена), то прямое удаление в SQL не создаст записей регистрации изменений. Это значит, что в другие базы (например, из УТ в Бухгалтерию) информация об удалении не попадет. Данные там останутся «висеть», что приведет к расхождениям в учете.
Также стоит учитывать серверный кэш. Сервер приложений 1С кэширует записи регистров. После выполнения SQL-запроса пользователи могут видеть старые данные в формах списков. Для решения этой проблемы может потребоваться перезапуск рабочих процессов сервера или выполнение метода ОбновитьПовторноИспользуемыеЗначения().
При выполнении команды DELETE на огромных объемах данных (миллионы строк) SQL Server может применить Lock Escalation (эскалацию блокировок). Вместо того чтобы блокировать конкретные строки, сервер заблокирует всю таблицу целиком. В это время ни один пользователь не сможет даже прочитать данные из этого регистра.
Разберем рекомендации по выполнению массовых операций:
DELETE TOP (N) для контроля нагрузки.У каждого документа-регистратора в его основной таблице (например, _Document123) могут быть скрытые поля-флаги, указывающие на наличие движений по конкретным регистрам. Если вы полностью удаляете все движения документа через SQL, платформа может продолжать считать, что движения есть, так как флаг в таблице документа не был сброшен. Это может привести к ошибкам при попытке удаления самого документа-регистратора стандартными средствами 1С.
Прямая работа с SQL — это мощный инструмент, который стоит использовать только тогда, когда штатные методы НаборЗаписей действительно не справляются. Проанализировав все риски, мы можем составить безопасный алгоритм действий:
_LineNo.Помните, что во многих случаях вместо DELETE выгоднее использовать TRUNCATE (если нужно очистить регистр полностью) или схему с переливом нужных данных в промежуточную таблицу с последующей подменой (RENAME), так как INSERT в SQL обычно работает быстрее, чем массовый DELETE.