Зачем нужен проверочный запрос после получения ссылки по UUID при интеграции и как это оптимизировать?

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

При разработке и поддержке механизмов интеграции (например, бесшовной интеграции между 1С:ERP Управление предприятием и 1С:Документооборот) программисты часто сталкиваются со специфическим поведением платформы 1С:Предприятие при работе со ссылочными типами данных. Типичный пример кода, который вызывает множество вопросов у начинающих разработчиков, содержит связку генерации ссылки по уникальному идентификатору (UUID) и последующий SQL-запрос к СУБД для проверки ее физического существования.

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

В чем суть проблемы? Анализ исходного кода

Давайте рассмотрим фрагмент кода, который используется в типовых конфигурациях для сопоставления внешних объектов, полученных из сторонней системы (в данном случае — 1С:Документооборот):


Для Каждого ВнешнийОбъект Из ВнешниеОбъекты Цикл
    
    Попытка
        Ссылка = СсылкаИзUUID(ВнешнийОбъект.type, ВнешнийОбъект.id);
    Исключение
        // Тип объекта не найден в текущей конфигурации.
        Продолжить;
    КонецПопытки;
    
    Если Ссылка = Неопределено Тогда
        Продолжить;
    КонецЕсли;
    
    Запрос = Новый Запрос(
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    Ссылка
        |ИЗ " + ВнешнийОбъект.type + "
        |ГДЕ
        |    Ссылка = &Ссылка");
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    
    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        Результат.Добавить(Ссылка);
    КонецЕсли;
    
КонецЦикла;

На первый взгляд, этот код выглядит избыточным. Функция СсылкаИзUUID сопоставляет имя типа метаданных и строковое представление уникального идентификатора, возвращая готовую ссылку:


Функция СсылкаИзUUID(Тип, UUID) Экспорт
    
    СсылкаНаПотребителя = Неопределено;
    
    Если СтрНачинаетсяС(Тип, "Справочник.") Тогда
        СсылкаНаПотребителя = Справочники[СтрЗаменить(Тип, "Справочник.", "")].ПолучитьСсылку(Новый УникальныйИдентификатор(UUID));
        
    ИначеЕсли СтрНачинаетсяС(Тип, "Документ.") Тогда
        СсылкаНаПотребителя = Документы[СтрЗаменить(Тип, "Документ.", "")].ПолучитьСсылку(Новый УникальныйИдентификатор(UUID));
        
    // ... аналогичные проверки для других классов метаданных
    
    КонецЕсли;
    
    Возврат СсылкаНаПотребителя;
    
КонецФункции

Возникает закономерный вопрос: если функция СсылкаИзUUID уже отработала успешно и вернула ссылку, зачем дополнительно выполнять запрос к базе данных? Разве сам факт возврата заполненной ссылки не гарантирует, что объект существует?

Внутреннее устройство платформы: как работает ПолучитьСсылку()

Чтобы понять причину необходимости запроса, нам потребуется заглянуть «под капот» платформы 1С:Предприятие. Рассмотрим, что происходит при вызове метода ПолучитьСсылку() менеджера справочника или документа.

Метод ПолучитьСсылку() работает исключительно в оперативной памяти (ОЗУ) сервера приложений или клиента 1С. Платформа выполняет чисто математическое конструирование объекта ссылки. Ссылка в 1С состоит из двух основных компонентов:

  1. Идентификатор таблицы метаданных (какому объекту конфигурации принадлежит ссылка, например, справочнику Контрагенты).
  2. Уникальный идентификатор (UUID) — 128-битное двоичное число, однозначно определяющее запись.

При вызове метода ПолучитьСсылку(Новый УникальныйИдентификатор(UUID)) платформа просто упаковывает эти данные в специальный внутренний тип данных — ссылку. При этом никакого обращения к СУБД (MS SQL, PostgreSQL и др.) не происходит!

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

Таким образом, функция СсылкаИзUUID() вернет вполне валидный с точки зрения типов объект ссылки даже в том случае, если в базе данных никогда не было объекта с таким идентификатором (или если он был безвозвратно удален в ходе очистки базы данных).

Зачем нужен проверочный запрос?

Теперь становится понятно, для чего разработчики фирмы «1С» добавили проверочный запрос. Его единственная цель — убедиться, что объект физически записан в таблице базы данных.

Когда мы выполняем запрос:


ВЫБРАТЬ ПЕРВЫЕ 1
    Ссылка
ИЗ Справочник.ИмяСправочника
ГДЕ
    Ссылка = &Ссылка

Платформа транслирует это обращение в низкоуровневый SQL-запрос (например, SELECT TOP 1 _IDRRef FROM _ReferenceXX WHERE _IDRRef = @Param). База данных ищет реальную строчку. Если запись найдена, запрос возвращает результат, и мы добавляем ссылку в массив корректных данных. Если запись была удалена или никогда не существовала, запрос вернет пустой результат.

Это самый быстрый и безопасный способ проверить существование объекта в базе данных, так как он не требует дорогостоящего чтения всех полей объекта из базы данных, которое произошло бы при попытке вызвать метод Ссылка.ПолучитьОбъект().

Влияние механизмов RLS (ограничение доступа на уровне записей)

Проанализируем еще один важнейший аспект проверки существования ссылок — безопасность и права доступа. В корпоративных системах, таких как 1С:ERP, активно используются механизмы RLS (Row-Level Security).

Если у текущего пользователя ограничены права на чтение конкретного объекта (например, контрагента определенного региона), то простой запрос к таблице под его сессией вернет пустой результат. Программа решит, что ссылка «битая» (не существует в базе), хотя на самом деле объект есть, просто у пользователя нет на него прав.

Чтобы избежать ложных срабатываний, любые сервисные проверки на физическое существование объектов в базе данных должны выполняться в привилегированном режиме. Это гарантирует, что запрос проверит физическое наличие записи в СУБД в обход ограничений RLS.

В типовых конфигурациях на базе БСП (Библиотеки стандартных подсистем) для этого предусмотрена специальная функция:

ОбщегоНазначения.СсылкаСуществует(Ссылка)

Она автоматически устанавливает привилегированный режим исполнения кода и отправляет быстрый запрос к СУБД.

Оптимизация производительности: проблема N+1 запросов

Внимательно посмотрим на исходный цикл из Сообщения 0. В нем присутствует классическая архитектурная ошибка производительности — запрос в цикле (проблема N+1).

Если во входящем массиве ВнешниеОбъекты будет содержаться 100 или 1000 элементов, система выполнит ровно столько же отдельных запросов к СУБД. Это создаст колоссальную и абсолютно ненужную нагрузку на сеть и сервер базы данных.

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

Рассмотрим пример оптимизированного кода, который решает эту задачу эффективно:


// Шаг 1: Быстро подготавливаем ссылки в ОЗУ и группируем их по типам таблиц
СоответствиеПоТипам = Новый Соответствие; // Ключ - ТипСтрокой, Значение - МассивСсылок

Для Каждого ВнешнийОбъект Из ВнешниеОбъекты Цикл
    Попытка
        Ссылка = СсылкаИзUUID(ВнешнийОбъект.type, ВнешнийОбъект.id);
        Если Ссылка <> Неопределено Тогда
            МассивСсылок = СоответствиеПоТипам.Получить(ВнешнийОбъект.type);
            Если МассивСсылок = Неопределено Тогда
                МассивСсылок = Новый Массив;
                СоответствиеПоТипам.Вставить(ВнешнийОбъект.type, МассивСсылок);
            КонецЕсли;
            МассивСсылок.Добавить(Ссылка);
        КонецЕсли;
    Исключение
        // Игнорируем типы объектов, которых нет в нашей конфигурации
        Продолжить;
    КонецПопытки;
КонецЦикла;

// Шаг 2: Проверяем существование объектов пачками через СУБД
Для Каждого КлючИЗначение Из СоответствиеПоТипам Цикл
    
    ИмяТаблицы = КлючИЗначение.Ключ;
    МассивДляПроверки = КлючИЗначение.Значение;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Таблица.Ссылка КАК Ссылка
        |ИЗ
        |    " + ИмяТаблицы + " КАК Таблица
        |ГДЕ
        |    Таблица.Ссылка В (&МассивДляПроверки)";
        
    Запрос.УстановитьПараметр("МассивДляПроверки", МассивДляПроверки);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Результат.Добавить(Выборка.Ссылка);
    КонецЦикла;
    
КонецЦикла;

Проанализируем, почему этот вариант работает значительно быстрее:

Использование готовых механизмов БСП

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

Библиотека стандартных подсистем предоставляет готовый высокопроизводительный инструмент для проверки существования массива разнородных ссылок — метод ОбщегоНазначения.ЗначениеРеквизитаОбъектов(). Этот метод берет на себя всю рутину по группировке ссылок разного типа, запуску пакетных запросов и возвращает результат в виде соответствия.

Посмотрим на пример изящной реализации этой задачи с использованием БСП:


// Шаг 1: Собираем все ссылки в один плоский массив в ОЗУ
МассивСсылокДляПроверки = Новый Массив;
Для Каждого ВнешнийОбъект Из ВнешниеОбъекты Цикл
    Попытка
        Ссылка = СсылкаИзUUID(ВнешнийОбъект.type, ВнешнийОбъект.id);
        Если ЗначениеЗаполнено(Ссылка) Тогда
            МассивСсылокДляПроверки.Добавить(Ссылка);
        КонецЕсли;
    Исключение
        Продолжить;
    КонецПопытки;
КонецЦикла;

// Шаг 2: Выполняем один оптимизированный пакетный запрос средствами БСП
// Метод вернет Соответствие, где ключами будут только реально существующие в БД ссылки
ТаблицаСуществующих = ОбщегоНазначения.ЗначениеРеквизитаОбъектов(МассивСсылокДляПроверки, "Ссылка");

// Шаг 3: Заполняем результат только существующими ссылками
Для Каждого Ссылка Из МассивСсылокДляПроверки Цикл
    Если ТаблицаСуществующих.Получить(Ссылка) <> Неопределено Тогда
        Результат.Добавить(Ссылка);
    КонецЕсли;
КонецЦикла;

Этот подход является наиболее предпочтительным в современной разработке на платформе 1С:Предприятие 8.3. Он лаконичен, безопасен с точки зрения RLS, полностью протестирован разработчиками фирмы «1С» и обеспечивает максимально возможную скорость взаимодействия сервера приложений с сервером баз данных.

← На главную