Объединение дублирующихся элементов справочника — стандартная задача для администратора или разработчика 1С. Однако, когда один из элементов встречается в базе данных сотни тысяч раз (например, более 500 000 ссылок), классические подходы и стандартные обработки БСП («Поиск и удаление дублей») гарантированно вызывают зависание системы, переполнение оперативной памяти сервера или блокировки таблиц СУБД. Поиск ссылок в таких объемах может занимать десятки часов, а сам процесс замены — приводить к аварийному завершению работы службы rphost.
В этой статье мы подробно проанализируем причины возникновения этой проблемы, рассмотрим архитектурные ограничения платформы 1С и разберем три практических метода решения задачи: использование профессионального инструментария, оптимизированный программный перенос по порциям и сверхбыструю (но рискованную) прямую замену на уровне SQL.
Прежде чем перейти к решениям, выясним причину, почему стандартные механизмы пасуют перед большими объемами данных:
НайтиПоСсылкам() выполняет сканирование всех таблиц базы данных, где теоретически может использоваться тип заменяемого справочника. На больших базах данных это приводит к колоссальному числу тяжелых запросов типа Full Table Scan.---
Если конфигурация позволяет использовать сторонние инструменты, настоятельно рекомендуется отказаться от стандартного поиска дублей БСП в пользу обработки «Поиск и замена ссылок» из состава программного комплекса «Инструменты разработчика» (ИР).
Разберем, почему этот инструмент эффективнее в нашей ситуации:
---
Если задачу необходимо решить программно, главная цель — обойти стандартные проверки и разбить процесс на небольшие порции (батчи). Рассмотрим по шагам, как написать эффективный скрипт замены.
Для минимизации нагрузки мы применим два важнейших правила:
ОбменДанными.Загрузка = Истина. Это отключит проверки при записи, проведение документов, расчеты регистров и тяжелые обработчики событий ПередЗаписью и ПриЗаписи.Ниже представлен пример шаблона кода для порционной замены ссылок в документах:
// Определяем заменяемый элемент и правильный элемент
СсылкаДубль = Справочники.Номенклатура.НайтиПоКоду("00001"); // тот, который удаляем
СсылкаОригинал = Справочники.Номенклатура.НайтиПоКоду("00002"); // тот, на который меняем
// Находим все ссылки на дубль
МассивДляПоиска = Новый Массив;
МассивДляПоиска.Добавить(СсылкаДубль);
ТаблицаСсылок = НайтиПоСсылкам(МассивДляПоиска);
// Фильтруем таблицу, оставляя только документы
ТаблицаДокументов = ТаблицаСсылок.Скопировать();
// При необходимости можно настроить отбор по конкретным метаданным
ВсегоСсылок = ТаблицаДокументов.Количество();
НомерСтроки = 0;
РазмерПорции = 1000; // Оптимальный размер порции для одной транзакции
Пока НомерСтроки < ВсегоСсылок Цикл
НачатьТранзакцию();
Для Итератор = 1 По РазмерПорции Цикл
Если НомерСтроки >= ВсегоСсылок Тогда
Прервать;
КонецЕсли;
СтрокаСсылки = ТаблицаДокументов[НомерСтроки];
ОбъектСсылка = СтрокаСсылки.Ссылка;
// Проверяем, что это действительно объект ссылочного типа документа
Если Метаданные.Документы.Содержит(ОбъектСсылка.Метаданные()) Тогда
Попытка
ДокументОбъект = ОбъектСсылка.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
// Включаем режим загрузки для отключения подписок и проведения
ДокументОбъект.ОбменДанными.Загрузка = Истина;
// Вызываем процедуру замены во всех реквизитах и табличных частях
// (Платформенный метод заполнения или ручной обход реквизитов)
ЗаменитьСсылкиВОбъекте(ДокументОбъект, СсылкаДубль, СсылкаОригинал);
ДокументОбъект.Записать();
КонецЕсли;
Исключение
// Логируем ошибки блокировок или доступа
ЗаписьЖурналаРегистрации("ЗаменаДублей.Ошибка", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
НомерСтроки = НомерСтроки + 1;
КонецЦикла;
ЗафиксироватьТранзакцию();
// Делаем небольшую технологическую паузу для СУБД
#Если Клиент Тогда
ОбработкаПрерыванияПользователя();
#КонецЕсли
КонецЦикла;
Для реализации функции ЗаменитьСсылкиВОбъекте можно использовать универсальный обход всех реквизитов шапки и строк табличных частей объекта метаданных через программный доступ к свойствам.
---
Прямое обновление данных на уровне СУБД позволяет выполнить замену 500 000 ссылок за несколько секунд или минут. Однако этот метод является недокументированным, нарушает лицензионное соглашение 1С и несет огромные риски повреждения логической целостности базы данных. Применять его можно только на клиент-серверном варианте работы, предварительно сделав полную резервную копию базы данных SQL!
Проанализируем особенности хранения ссылок 1С в таблицах SQL и шаги, необходимые для безопасного выполнения операции:
В базах данных SQL Server ссылки 1С хранятся в бинарных полях типа binary(16). При этом платформа 1С записывает уникальный идентификатор (UUID) в инвертированном (перевернутом) виде. Чтобы написать корректный запрос UPDATE, вам необходимо программно перевести строковый GUID 1С в HEX-последовательность Microsoft SQL Server.
Смещение байтов происходит по определенному алгоритму (байты переставляются местами группами: первые три блока UUID переворачиваются задом наперед, последние блоки сохраняют свой порядок).
Имена таблиц в SQL-базе генерируются платформой динамически (например, _Reference45 для справочников или _Document120_VT1305 для табличных частей). Чтобы узнать, в каких именно таблицах и колонках базы данных SQL используется заменяемый справочник, воспользуйтесь программным методом 1С:
ПолучитьСтруктуруХраненияБазыДанных()
Этот метод вернет точное соответствие имен метаданных 1С и физических таблиц СУБД. На основе этих данных формируется список SQL-запросов на замену.
После получения HEX-представлений старого (дубля) и нового (оригинального) GUID, запрос в SQL Management Studio для конкретной таблицы (например, табличной части документа) будет выглядеть следующим образом:
-- ПРИМЕР ЗАПРОСА В СУБД SQL
-- @OldHex - двоичное представление заменяемого GUID
-- @NewHex - двоичное представление оригинального GUID
UPDATE _Document120_VT1305
SET _Fld1306RRef = 0x80E100155D05070211E69CB2CD0C5BB3 -- Новый HEX
WHERE _Fld1306RRef = 0x80E100155D05070211E69CB2CD0C5BA2 -- Старый HEX
Сервер приложений 1С активно кэширует данные объектов в своей оперативной памяти. Если вы измените значения напрямую в SQL, сервер 1С об этом изменении ничего не узнает. В результате пользователи продолжат видеть старый дубль, а при записи объектов возникнут конфликты блокировок и ошибки несоответствия версий.
Чтобы изменения корректно применились, обязательно выполните следующие действия сразу после SQL-апдейта:
rphost) для очистки кэша сеансов.Также помните, что если для справочника или документов включена функция «История данных» (версионирование), прямая SQL-замена не обновит исторические XML-версии объектов. Это может привести к ошибкам при попытке просмотреть историю изменений в интерфейсе программы.
---
Выбор оптимального метода напрямую зависит от времени, которым вы располагаете, и вашей квалификации: