Как оптимизировать обход выборки изменений плана обмена при работе с RLS?

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

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

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

В чем суть проблемы производительности?

Типичный код получения измененных объектов выглядит следующим образом:


ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, ЗаписьСообщения.НомерСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
    Попытка
        Данные = ВыборкаИзменений.Получить();
    Исключение
        // Игнорируем объекты, на которые у текущего пользователя нет прав
        Продолжить;
    КонецПопытки;
    // Дальнейшая обработка и экспорт данных...
КонецЦикла;

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

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

  1. Отправку запроса к СУБД для чтения объекта.
  2. Проверку прав СУБД и возврат ошибки доступа.
  3. Формирование программного исключения платформой 1С.
  4. Передачу управления в блок Исключение.

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

---

Решение 1. Использование привилегированного режима (самый быстрый способ)

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

Для этого мы воспользуемся вызовом кода в привилегированном режиме. Выполним оптимизацию кода:


УстановитьПривилегированныйРежим(Истина);

ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, ЗаписьСообщения.НомерСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
    // В привилегированном режиме проверка RLS отключается, 
    // метод Получить() отработает максимально быстро без генерации исключений.
    Данные = ВыборкаИзменений.Получить();
    
    // Выполняем обработку данных
    ...
КонецЦикла;

УстановитьПривилегированныйРежим(Ложь);

Преимущества решения:

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

---

Решение 2. Динамический запрос с ключевым словом РАЗРЕШЕННЫЕ и фильтрация выборки

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

Мы можем пойти по другому пути: отсечь все запрещенные объекты еще до того, как начнем их обходить методом Получить(). Для этого отлично подойдет механизм запросов платформы 1С с использованием ключевого слова РАЗРЕШЕННЫЕ.

Чтобы не писать сложный статичный запрос вручную, мы разработаем алгоритм, который динамически строит текст запроса на основании состава конкретного плана обмена. Рассмотрим этот пример подробно:


// Шаг 1. Получаем состав плана обмена
ПланОбменаМетаданные = Метаданные.ПланыОбмена.МойПланОбмена;
ТаблицыЗапроса = Новый Массив;

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

// Шаг 2. Объединяем все подзапросы в один общий запрос
ТекстЗапроса = СтрСоединить(ТаблицыЗапроса, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС);

Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("Узел", УзелОбмена);

// Шаг 3. Выгружаем только разрешенные ссылки
РезультатЗапроса = Запрос.Выполнить();
ТаблицаРазрешенных = РезультатЗапроса.Выгрузить();
МассивРазрешенныхСсылок = ТаблицаРазрешенных.ВыгрузитьКолонку("Ссылка");

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

Почему это работает эффективно: СУБД выполняет оптимизированную выборку из таблиц регистрации изменений, накладывая правила RLS на уровне SQL-запроса. В результирующий массив МассивРазрешенныхСсылок попадают только доступные пользователю объекты. При вызове метода ВыбратьИзменения платформа отфильтрует выборку по переданному массиву, полностью исключая возникновение ошибок доступа при вызове метода Получить().

---

Решение 3. Оптимизация на этапе регистрации изменений (Подписки на события)

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

Разберем, как уйти от автоматической регистрации к управляемой:

  1. В свойствах состава плана обмена установите свойство Авторегистрация в значение Запретить для тех объектов, доступ к которым ограничен.
  2. Создайте общую Подписку на событие (например, ПередЗаписью или ПриЗаписи для нужных типов объектов).
  3. В обработчике подписки пропишите логику программной регистрации изменений только для тех узлов, которые действительно должны получить эти данные.

Посмотрим на пример кода в модуле подписки на событие:


Процедура ПередЗаписьюРегистрацияИзменений(Источник, Отказ) Экспорт
    
    Если Источник.ОбменДанными.Загрузка Тогда
        Возврат; // Не регистрируем изменения, если объект записывается в процессе импорта
    КонецЕсли;
    
    СсылкаНаОбъект = Источник.Ссылка;
    
    // Проверяем доступность объекта для получателей обмена
    Для Каждого УзелОбмена Из СписокУзловДляПроверки() Цикл
        
        // Здесь реализуется логика проверки прав доступа или бизнес-правил обмена
        Если ОбъектДоступенДляУзла(СсылкаНаОбъект, УзелОбмена) Тогда
            ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, СсылкаНаОбъект);
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

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

---

Решение 4. Прямой отказ от ВыбратьИзменения в пользу запросов и ручной очистки

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

Рассмотрим эту схему работы по шагам:


// Шаг 1. Получаем список измененных разрешенных объектов напрямую из таблиц изменений
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ РАЗРЕШЕННЫЕ
    |	НоменклатураИзменения.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Номенклатура.Изменения КАК НоменклатураИзменения
    |ГДЕ
    |	НоменклатураИзменения.Узел = &Узел";

Запрос.УстановитьПараметр("Узел", УзелОбмена);
РезультатВыбора = Запрос.Выполнить().Выгрузить();

МассивОбъектовДляВыгрузки = РезультатВыбора.ВыгрузитьКолонку("Ссылка");

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

// Шаг 3. Удаляем регистрацию изменений вручную по выгруженным объектам
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, МассивОбъектовДляВыгрузки);

Почему этот подход заслуживает внимания:

Резюме

Мы проанализировали причины замедления работы обмена данными при использовании RLS и рассмотрели 4 способа решения проблемы. Выбор конкретного метода зависит от требований к безопасности и архитектуры вашего решения:

  1. Если права проверять не требуется — используйте Привилегированный режим (Решение 1).
  2. Если права пользователя важны и критичны — используйте динамический запрос с РАЗРЕШЕННЫЕ и фильтрацией (Решение 2) или прямой обход изменений (Решение 4).
  3. Если вы хотите решить проблему глобально и разгрузить базу данных — перенесите логику фильтрации на этап регистрации изменений в подписках на события (Решение 3).
← На главную