В практике разработки на платформе 1С:Предприятие часто возникает задача программного поиска ссылок на определенный объект. Это необходимо при контроле целостности данных (см. контроль ссылочной целостности и удаление объектов 1С), при запрете удаления используемых элементов или при анализе связанных данных. Стандартный метод НайтиСсылки (или его глобальный аналог НайтиПоСсылкам) на больших базах данных может работать критически медленно, блокируя таблицы и заставляя пользователя ждать. В таких случаях часто требуется поиск ссылок на объект с ограничением количества выводимых записей, чтобы не перегружать систему. В этой статье мы подробно разберем, как можно ускорить этот процесс, какие существуют альтернативы и как правильно ограничить область поиска для достижения максимальной производительности.
Прежде чем мы перейдем к оптимизации, давайте разберем, как работает стандартный механизм. Когда мы вызываем функцию:
СсылкиНаОбъект = НайтиПоСсылкам(МассивСсылок);
Платформа начинает сканировать все таблицы базы данных, в которых потенциально может храниться ссылка на искомый тип объекта. Это означает, что если вы ищете ссылку на элемент справочника "Номенклатура", система проверит не только документы "Реализация товаров", но и все регистры сведений, регистры накопления, задачи, бизнес-процессы и даже историю версий объектов.
На больших базах с миллионами записей в регистрах или журналах регистрации изменений это приводит к полному сканированию таблиц (Table Scan), что и является основной причиной "тормозов".
Самый эффективный и "штатный" способ ускорения, который мы рассмотрим в первую очередь — это использование второго параметра метода НайтиПоСсылкам. Этот параметр позволяет передать массив метаданных, ограничивая круг поиска только теми таблицами, которые нам действительно интересны. Для работы в различных интерфейсах удобно применять универсальный поиск ссылок в документах и справочниках, адаптированный под управляемые и обычные формы.
Разберем ситуацию, когда нам не нужно искать ссылки в регистрах версионирования, в присоединенных файлах или в архивных документах прошлых лет. Мы можем программно сформировать список таблиц для поиска.
Давайте посмотрим, как это реализовать на практике. Мы создадим алгоритм, который ищет ссылки только в документах и определенных справочниках, игнорируя регистры.
// Создаем массив ссылок, которые ищем
ИскомыеСсылки = Новый Массив;
ИскомыеСсылки.Добавить(СсылкаНаОбъект);
// Формируем область поиска - массив объектов метаданных
ОбластьПоиска = Новый Массив;
// Проходим по всем документам конфигурации
Для Каждого ДокМетаданные Из Метаданные.Документы Цикл
// Здесь можно добавить дополнительную логику, например, исключить архивные документы
Если Не ДокМетаданные.Имя = "АрхивСтарыхДанных" Тогда
ОбластьПоиска.Добавить(ДокМетаданные);
КонецЕсли;
КонецЦикла;
// Добавляем определенные справочники, где нас интересуют ссылки
ОбластьПоиска.Добавить(Метаданные.Справочники.ДоговорыКонтрагентов);
// Выполняем оптимизированный поиск
Результат = НайтиПоСсылкам(ИскомыеСсылки, ОбластьПоиска);
Этот подход позволяет сократить время выполнения в десятки раз, так как SQL-серверу не нужно обращаться к "тяжелым" таблицам логов и версий.
В реальных задачах перечислять все таблицы вручную неудобно и чревато ошибками (можно забыть новую таблицу). Поэтому давайте рассмотрим более продвинутый метод: мы будем программно анализировать метаданные и собирать только те таблицы, которые содержат реквизиты нужного нам типа. Для визуализации таких связей отлично подходит автоматический анализ метаданных и построение графа зависимостей ссылок.
Это "умный" фильтр. Мы проанализируем структуру конфигурации и отберем только релевантные таблицы.
Функция НайтиСсылкиОптимизировано(ИскомаяСсылка)
ТипИскомого = ТипЗнч(ИскомаяСсылка);
ОбластьПоиска = Новый Массив;
// Перебираем все справочники
Для Каждого МД Из Метаданные.Справочники Цикл
Если ИмеетРеквизитНужногоТипа(МД, ТипИскомого) Тогда
ОбластьПоиска.Добавить(МД);
КонецЕсли;
КонецЦикла;
// Перебираем документы
Для Каждого МД Из Метаданные.Документы Цикл
// Исключаем, например, документы версионирования
Если СтрНайти(МД.Имя, "Версионирование") > 0 Тогда
Продолжить;
КонецЕсли;
Если ИмеетРеквизитНужногоТипа(МД, ТипИскомого) Тогда
ОбластьПоиска.Добавить(МД);
КонецЕсли;
КонецЦикла;
// Аналогично для регистров...
Возврат НайтиПоСсылкам(Новый Массив{ИскомаяСсылка}, ОбластьПоиска);
КонецФункции
Функция ИмеетРеквизитНужногоТипа(ОбъектМД, ТипИскомого)
// Упрощенный пример проверки реквизитов
Для Каждого Реквизит Из ОбъектМД.Реквизиты Цикл
Если Реквизит.Тип.СодержитТип(ТипИскомого) Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
// Также нужно проверять табличные части!
Для Каждого ТЧ Из ОбъектМД.ТабличныеЧасти Цикл
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
Если РеквизитТЧ.Тип.СодержитТип(ТипИскомого) Тогда
Возврат Истина;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат Ложь;
КонецФункции
Такой подход позволяет нам исключить заведомо ненужные таблицы, но при этом гарантировать, что мы проверим все бизнес-сущности.
Если нам известна точная структура данных, то самым быстрым способом часто оказывается прямой запрос к базе данных через язык запросов 1С. Механизм НайтиПоСсылкам — это универсальный "черный ящик", который выполняет множество проверок. Запрос же действует прямолинейно.
Мы можем сформировать текст запроса, который объединит выборки из всех интересующих нас таблиц. Рассмотрим пример концепции:
ТекстЗапроса = "";
Первый = Истина;
// Пример для двух таблиц, на практике этот текст собирается в цикле по метаданным
СписокТаблиц = Новый Структура("Справочник.ДоговорыКонтрагентов, Документ.РеализацияТоваровУслуг");
Для Каждого Эл Из СписокТаблиц Цикл
ИмяТаблицы = Эл.Ключ; // Например "Справочник.ДоговорыКонтрагентов"
Если Не Первый Тогда
ТекстЗапроса = ТекстЗапроса + "
|ОБЪЕДИНИТЬ ВСЕ
|";
КонецЕсли;
ТекстЗапроса = ТекстЗапроса +
"ВЫБРАТЬ
| Ссылка КАК Ссылка,
| """ + ИмяТаблицы + """ КАК МетаданныеПредставление
|ИЗ
| " + ИмяТаблицы + " КАК Таб
|ГДЕ
| Таб.Контрагент = &ИскомаяСсылка"; // В реальном коде имя поля "Контрагент" нужно определять динамически
Первый = Ложь;
КонецЦикла;
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ИскомаяСсылка", СсылкаНаКонтрагента);
Результат = Запрос.Выполнить().Выгрузить();
Преимущества метода запроса:
НайтиПоСсылкам проверяет ссылочную целостность. Запрос же найдет GUID ссылки в поле, даже если самого объекта уже не существует. В таких случаях часто требуется поиск и замена битых ссылок справочников для восстановления порядка в базе.Однако, у этого метода есть недостаток: ограничения на длину текста запроса и сложность динамического формирования текста для табличных частей и реквизитов составного типа.
Если вам нужно постоянно искать ссылки на одни и те же типы объектов (например, "Документы по Контрагенту"), то идеальным архитектурным решением будет создание объекта конфигурации КритерииОтбора.
Давайте разберем, почему это эффективно:
Использовать их очень просто:
// Получаем выборку документов по критерию
Выборка = КритерииОтбора.СвязанныеДокументы.Выбрать(ИскомаяСсылка);
Пока Выборка.Следующий() Цикл
// Обработка найденной ссылки
КонецЦикла;
Минус этого подхода в том, что он требует изменения конфигурации в режиме Конфигуратора и последующей реструктуризации базы данных для построения индекса. Это не подходит для разовых задач поиска произвольных объектов.
В сообществе 1С (на форумах и Инфостарте) существуют готовые решения, например, подсистема "Инструменты разработчика" (ИР) — для этого подойдёт универсальная подсистема Инструменты разработчика для 1С. В ней реализован собственный алгоритм поиска ссылок.
Особенности реализации поиска в таких инструментах часто включают:
_RRef), но требует прав администратора СУБД.При использование любого из методов, обратите внимание на следующие моменты:
Индексировать.ФоновоеЗадание. Это не ускорит сам поиск, но интерфейс программы не "зависнет", и пользователь сможет продолжать работу.Если вам нужно искать быстрее, чем НайтиСсылки:
НайтиПоСсылкам с передачей массива метаданных (ОбластьПоиска), исключив логи, историю и версионирование.