При разработке и поддержке систем обмена данными в среде 1С:Предприятие разработчики часто сталкиваются со стандартным шаблоном выгрузки измененных объектов. Этот процесс базируется на использовании планов обмена и метода ВыбратьИзменения. Однако стандартный подход начинает работать крайне медленно, когда в конфигурации активно используются механизмы разграничения прав доступа на уровне записей — RLS (Row-Level Security).
В этой статье мы подробно проанализируем, почему возникает данная проблема производительности, разберем ее внутренние причины и рассмотрим несколько эффективных способов оптимизации обхода выборки изменений, начиная от простых платформенных решений и заканчивая изменением архитектуры регистрации данных.
Типичный код получения измененных объектов выглядит следующим образом:
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, ЗаписьСообщения.НомерСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
Попытка
Данные = ВыборкаИзменений.Получить();
Исключение
// Игнорируем объекты, на которые у текущего пользователя нет прав
Продолжить;
КонецПопытки;
// Дальнейшая обработка и экспорт данных...
КонецЦикла;
Если для текущего пользователя настроены правила RLS, ограничивающие чтение определенных документов или справочников, метод Получить() при попытке прочитать недоступный объект вызывает исключение «Нарушение прав доступа». Конструкция Попытка...Исключение успешно перехватывает эту ошибку и позволяет коду продолжить работу.
Выясним причину, почему это происходит медленно. Дело в том, что генерация и обработка исключений в платформе 1С — крайне ресурсоемкая операция. Когда измененных объектов тысячи, а прав нет на сотни из них, сервер 1С тратит колоссальное количество времени на:
Исключение.В результате процесс обмена, который мог бы занимать несколько секунд, растягивается на минуты или даже часы. Нам необходимо исключить саму возможность возникновения этих исключений.
---
Проанализируем ситуацию: действительно ли пользователю, под которым запускается обмен, нужно ограничивать выгружаемые данные? Если обмен является техническим (например, синхронизация между филиалами или базами одной компании) и должен переносить абсолютно все зарегистрированные изменения независимо от прав текущего инициатора, мы можем полностью отключить проверку прав RLS на время обхода выборки.
Для этого мы воспользуемся вызовом кода в привилегированном режиме. Выполним оптимизацию кода:
УстановитьПривилегированныйРежим(Истина);
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, ЗаписьСообщения.НомерСообщения);
Пока ВыборкаИзменений.Следующий() Цикл
// В привилегированном режиме проверка RLS отключается,
// метод Получить() отработает максимально быстро без генерации исключений.
Данные = ВыборкаИзменений.Получить();
// Выполняем обработку данных
...
КонецЦикла;
УстановитьПривилегированныйРежим(Ложь);
Преимущества решения:
Важное ограничение: Данный способ подходит только тогда, когда бизнес-логика обмена позволяет передавать все данные без оглядки на ограничения прав текущего пользователя.
---
Что делать, если обмен запускается обычным пользователем, и регламент безопасности строго запрещает выгружать те данные, к которым у пользователя нет доступа? В этом случае привилегированный режим использовать нельзя.
Мы можем пойти по другому пути: отсечь все запрещенные объекты еще до того, как начнем их обходить методом Получить(). Для этого отлично подойдет механизм запросов платформы 1С с использованием ключевого слова РАЗРЕШЕННЫЕ.
Чтобы не писать сложный статичный запрос вручную, мы разработаем алгоритм, который динамически строит текст запроса на основании состава конкретного плана обмена. Рассмотрим этот пример подробно:
// Шаг 1. Получаем состав плана обмена
ПланОбменаМетаданные = Метаданные.ПланыОбмена.МойПланОбмена;
ТаблицыЗапроса = Новый Массив;
Для Каждого ЭлементСостава Из ПланОбменаМетаданные.Состав Цикл
// Нас интересуют только те объекты, для которых включена авторегистрация
// или которые могут быть зарегистрированы на данном узле
ПолноеИмяОбъекта = ЭлементСостава.Метаданные.ПолноеИмя();
ИмяТаблицыИзменений = ПолноеИмяОбъекта + ".Изменения";
ТекстПодзапроса =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ТаблицаИзменений.Ссылка КАК Ссылка
|ИЗ
| " + ИмяТаблицыИзменений + " КАК ТаблицаИзменений
|ГДЕ
| ТаблицаИзменений.Узел = &Узел";
ТаблицыЗапроса.Добавить(ТекстПодзапроса);
КонецЦикла;
// Шаг 2. Объединяем все подзапросы в один общий запрос
ТекстЗапроса = СтрСоединить(ТаблицыЗапроса, Символы.ПС + "ОБЪЕДИНИТЬ ВСЕ" + Символы.ПС);
Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("Узел", УзелОбмена);
// Шаг 3. Выгружаем только разрешенные ссылки
РезультатЗапроса = Запрос.Выполнить();
ТаблицаРазрешенных = РезультатЗапроса.Выгрузить();
МассивРазрешенныхСсылок = ТаблицаРазрешенных.ВыгрузитьКолонку("Ссылка");
// Шаг 4. Передаем массив разрешенных ссылок в качестве фильтра в метод ВыбратьИзменения
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(УзелОбмена, ЗаписьСообщения.НомерСообщения, МассивРазрешенныхСсылок);
Пока ВыборкаИзменений.Следующий() Цикл
// Теперь здесь гарантированно будут только те объекты, на чтение которых у пользователя есть права
Данные = ВыборкаИзменений.Получить();
...
КонецЦикла;
Почему это работает эффективно: СУБД выполняет оптимизированную выборку из таблиц регистрации изменений, накладывая правила RLS на уровне SQL-запроса. В результирующий массив МассивРазрешенныхСсылок попадают только доступные пользователю объекты. При вызове метода ВыбратьИзменения платформа отфильтрует выборку по переданному массиву, полностью исключая возникновение ошибок доступа при вызове метода Получить().
---
Наиболее правильным решением с точки зрения архитектуры системы является предотвращение избыточной регистрации изменений. Если пользователь изначально не имеет прав на объект, то, возможно, этому пользователю (или на данный узел обмена) объект вообще не должен регистрироваться.
Разберем, как уйти от автоматической регистрации к управляемой:
ПередЗаписью или ПриЗаписи для нужных типов объектов).Посмотрим на пример кода в модуле подписки на событие:
Процедура ПередЗаписьюРегистрацияИзменений(Источник, Отказ) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат; // Не регистрируем изменения, если объект записывается в процессе импорта
КонецЕсли;
СсылкаНаОбъект = Источник.Ссылка;
// Проверяем доступность объекта для получателей обмена
Для Каждого УзелОбмена Из СписокУзловДляПроверки() Цикл
// Здесь реализуется логика проверки прав доступа или бизнес-правил обмена
Если ОбъектДоступенДляУзла(СсылкаНаОбъект, УзелОбмена) Тогда
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, СсылкаНаОбъект);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Благодаря этому подходу база данных избавляется от «мусорной» регистрации. При выгрузке изменений на узлах всегда будет находиться только актуальная и разрешенная к передаче информация, что снизит нагрузку как на процесс выгрузки, так и на объем передаваемых файлов сообщений обмена.
---
В высоконагруженных системах стандартный метод ВыбратьИзменения часто критикуют за жесткие блокировки СУБД таблиц регистрации изменений на период транзакции выгрузки. Чтобы полностью контролировать этот процесс, разработчики используют обход изменений через прямые запросы к таблицам регистрации с последующим ручным удалением регистрации.
Рассмотрим эту схему работы по шагам:
// Шаг 1. Получаем список измененных разрешенных объектов напрямую из таблиц изменений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| НоменклатураИзменения.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура.Изменения КАК НоменклатураИзменения
|ГДЕ
| НоменклатураИзменения.Узел = &Узел";
Запрос.УстановитьПараметр("Узел", УзелОбмена);
РезультатВыбора = Запрос.Выполнить().Выгрузить();
МассивОбъектовДляВыгрузки = РезультатВыбора.ВыгрузитьКолонку("Ссылка");
// Шаг 2. Выполняем сериализацию данных в XML / JSON
Для Каждого СсылкаИзМассива Из МассивОбъектовДляВыгрузки Цикл
// Здесь мы безопасно получаем данные, зная, что права на них точно есть
ОбъектДанных = СсылкаИзМассива.ПолучитьОбъект();
Если ОбъектДанных <> Неопределено Тогда
ЗаписатьДанныеВСообщение(ОбъектДанных, ЗаписьСообщения);
КонецЕсли;
КонецЦикла;
// Шаг 3. Удаляем регистрацию изменений вручную по выгруженным объектам
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, МассивОбъектовДляВыгрузки);
Почему этот подход заслуживает внимания:
ВЫБРАТЬ ПЕРВЫЕ... в запросе для пакетной обработки).ПланыОбмена.ВыбратьИзменения().РАЗРЕШЕННЫЕ на уровне запроса отсекает все нежелательные элементы, обеспечивая максимальную безопасность и быстродействие системы.Мы проанализировали причины замедления работы обмена данными при использовании RLS и рассмотрели 4 способа решения проблемы. Выбор конкретного метода зависит от требований к безопасности и архитектуры вашего решения: