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

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

В процессе сопровождения крупных баз данных 1С программисты часто сталкиваются с необходимостью массового изменения реквизитов в сотнях тысяч или миллионах документов. Выполнение такой операции стандартными средствами 1С «в лоб» (через перебор ссылок, получение объекта и его перезапись) может занимать дни или даже недели. В таких случаях возникает соблазн обратиться напрямую к СУБД и выполнить обновление одной командой UPDATE.

В этой статье мы подробно разберем, как решить эту задачу двумя способами: напрямую через MS SQL Server (с учетом специфики хранения ссылок 1С в бинарном формате) и с помощью оптимизированных штатных механизмов платформы 1С, обеспечивающих максимальную скорость и безопасность данных.

Способ №1. Прямое обновление через MS SQL (Быстро, но требует осторожности)

Главная сложность при написании прямых SQL-запросов к базе данных 1С заключается в том, что ссылочные типы данных (такие как подразделения, склады, контрагенты) на уровне СУБД хранятся в специальном бинарном формате binary(16). Если вы попробуете прочитать эти данные через внешнее соединение (например, ADO), СУБД вернет их в виде массива байтов COMSafeArray. Чтобы выполнить успешный апдейт, нам нужно работать с шестнадцатеричным представлением ссылки.

Проанализируем пошагово процесс подготовки и выполнения прямого SQL-запроса для установки нужного подразделения в документах.

Шаг 1. Получение имен таблиц и полей в СУБД

Имена таблиц документов и их реквизитов в SQL не совпадают с именами в конфигураторе 1С. Для того чтобы узнать реальные имена таблиц СУБД, воспользуемся штатным методом платформы ПолучитьСтруктуруХраненияБазыДанных() или специализированными обработками для просмотра структуры БД.

Шаг 2. Определение бинарного GUID нужного элемента справочника

Для обновления записей нам нужно знать внутренний идентификатор (GUID) того элемента, который мы хотим записать. Допустим, нам нужно установить подразделение с кодом «0000005». Нам необходимо найти его бинарную ссылку _IDRRef из таблицы соответствующего справочника.

Выполним SQL-запрос для поиска нужного подразделения:


SELECT _IDRRef, _Description 
FROM _ReferenceXXX 
WHERE _Code = '0000005';

Где _ReferenceXXX — имя таблицы справочника подразделений в вашей базе SQL. В результате запроса в поле _IDRRef мы получим бинарное значение вида 0x80DB00155D05070111E535BFC52E070F.

Шаг 3. Учет особенности Byte Swapping (Перестановка байт GUID)

Важное предупреждение: Платформа 1С хранит уникальные идентификаторы (GUID) в СУБД в измененном порядке байтов. Если вы берете текстовый GUID из 1С, его группы байтов меняют свой порядок при записи в SQL по следующему принципу: группы 1, 2 и 3 записываются задом наперед, а группы 4 и 5 остаются без изменений.

Поэтому самый безопасный способ получить точный бинарный GUID для запроса — это считать его из таблицы самого справочника на уровне SQL, как описано в Шаге 2, а не пытаться конвертировать строковое представление вручную.

Шаг 4. Написание и выполнение SQL-запроса UPDATE

Теперь мы можем составить итоговый запрос для обновления документов. Чтобы сделать запрос более изящным и избежать ошибок ручного копирования бинарных строк, мы можем объявить локальную переменную прямо в T-SQL, считать в нее значение ссылки и передать в обновление:


-- Объявляем переменную для хранения бинарного идентификатора подразделения
DECLARE @TargetDepartment BINARY(16);

-- Получаем идентификатор нужного подразделения по его коду
SELECT @TargetDepartment = _IDRRef 
FROM _ReferenceXXX 
WHERE _Code = '0000005';

-- Выполняем массовое обновление документов
UPDATE _DocumentYYY
SET _FldZZZRef = @TargetDepartment
WHERE _FldZZZRef = 0x00000000000000000000000000000000;

В этом запросе:

  1. _DocumentYYY — имя таблицы документов нужного типа в СУБД.
  2. _FldZZZRef — имя колонки «Подразделение» в таблице документов.
  3. 0x00000000000000000000000000000000 — бинарное представление пустой ссылки в 1С. Наше условие WHERE обновит только те документы, где подразделение еще не заполнено.

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

Опасности и последствия прямого обновления SQL

Несмотря на колоссальную скорость, прямое вмешательство в базу данных на уровне СУБД нарушает философию платформы 1С и несет огромные риски:

  1. Проблема кэширования: Сервер приложений 1С кэширует данные в оперативной памяти (процесс rphost). Изменив данные напрямую в SQL, сервер 1С об этом не узнает. Пользователи продолжат видеть старые данные до очистки кэша или перезапуска службы сервера приложений 1С.
  2. Логическая рассинхронизация: Если реквизит «Подразделение» дублируется в табличных частях документов или влияет на движения регистров накопления и бухгалтерии, простой апдейт шапки документа приведет к расхождению данных в отчетах.
  3. Проблемы с обменами (РИБ): Изменения, сделанные напрямую в SQL, не регистрируются на планах обмена. Эти документы никогда не будут отправлены в подчиненные узлы или другие базы данных в рамках синхронизации.

Если вы все же решились на этот шаг, обязательно сделайте резервную копию базы данных, а после выполнения запроса выполните полную очистку кэша сервера приложений и перепроведите измененные документы.

Способ №2. Оптимизированная пакетная запись в 1С (Безопасно и быстро)

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

Использование транзакций и пакетного режима записи

Основное замедление при записи документов по одному происходит из-за того, что СУБД тратит время на открытие транзакции, запись лога транзакций (файл .ldf) и фиксацию изменений на диске для каждого отдельного объекта.

Объединим запись документов в пакеты (например, по 1000–5000 штук) и выполним их внутри одной транзакции. Также обязательно отключим проведение и выполнение бизнес-логики, используя свойство ОбменДанными.Загрузка = Истина.

Рассмотрим пример кода оптимизированной обработки:


// Определяем размер пакета для одной транзакции
РазмерПакета = 2000;
Счетчик = 0;

НачатьТранзакцию();

Для Каждого СтрокаДокумента Из ТаблицаДокументов Цикл
    
    // Получаем объект документа без лишних проверок
    ДокументОбъект = СтрокаДокумента.Ссылка.ПолучитьОбъект();
    
    Если ДокументОбъект.Подразделение <> НужноеПодразделение Тогда
        ДокументОбъект.Подразделение = НужноеПодразделение;
        
        // Отключаем обработчики событий, подписки и проверки бизнес-логики
        ДокументОбъект.ОбменДанными.Загрузка = Истина;
        
        Попытка
            ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
            Счетчик = Счетчик + 1;
        Исключение
            // Логируем ошибку записи конкретного документа
            ЗаписьЖурналаРегистрации("МассовоеОбновление", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        КонецПопытки;
    КонецЕсли;
    
    // Фиксируем транзакцию при достижении размера пакета и открываем новую
    Если Счетчик % РазмерПакета = 0 Тогда
        ЗафиксироватьТранзакцию();
        ОбработкаПрерыванияПользователя();
        НачатьТранзакцию();
    КонецЕсли;
    
КонецЦикла;

// Фиксируем оставшиеся документы в последней транзакции
Если ТранзакцияАктивна() Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

Этот простой прием с транзакциями ускоряет работу алгоритма в десятки раз по сравнению с обычной построчной записью, поскольку диск СУБД совершает в тысячи раз меньше операций позиционирования головок и записи логов.

Многопоточность через фоновые задания

Если база данных работает на мощном сервере, а один поток выполнения обработки нагружает процессор лишь на несколько процентов, мы можем задействовать свободные ресурсы сервера, распределив задачу на несколько параллельных потоков с помощью механизма фоновых заданий.

Разберем логику многопоточной обработки:

  1. Разбиваем весь массив документов на несколько частей (например, по видам документов, по диапазонам дат или по первым символам идентификатора GUID).
  2. Для каждого пула документов запускаем фоновое задание с помощью метода ФоновыеЗадания.Выполнить().
  3. Каждое фоновое задание работает в своей транзакции и обрабатывает выделенную ему порцию данных независимо от остальных.

При использовании 8–16 параллельных потоков (в зависимости от количества ядер процессора сервера) скорость выполнения операции возрастает кратно, и миллионные объемы документов могут быть обработаны за несколько часов ночного технологического окна, полностью исключая риски повреждения структуры СУБД напрямую.

← На главную