Почему тормозит динамический список при RLS и как это исправить?

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

Разработчики систем на платформе 1С:Предприятие часто сталкиваются с ситуацией, когда без ограничений прав на уровне записей (RLS) система работает исключительно быстро, но при включении механизмов разграничения прав динамические списки начинают "зависать" на десятки и сотни секунд. Особенно остро эта проблема проявляется на таблицах с объемом данных более 100 000 записей.

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

---

Почему "падает" производительность динамического списка с RLS?

Для того чтобы понять причину зависания, необходимо проанализировать, как именно платформа 1С взаимодействует с СУБД при выводе данных в динамический список. Когда пользователь открывает форму списка, платформа отправляет в СУБД запрос, к которому динамически "пристраивается" условие ограничения прав (текст RLS).

Основными причинами падения скорости работы являются следующие факторы:

  1. Использование логического оператора ИЛИ (OR) в условиях ограничения. Использование сложных конструкций в шаблонах, таких как #ПоЗначениямИНаборамРасширенный, приводит к генерации тяжелых SQL-запросов со множеством ветвлений. Для оптимизатора СУБД (будь то MS SQL Server или PostgreSQL) наличие ИЛИ в условиях соединения таблиц или фильтрации часто делает невозможным использование индексов. СУБД отказывается от поиска по индексу (Index Seek) и переходит к полному сканированию таблицы (Table Scan / Clustered Index Scan).
  2. Постраничная выборка (порционность) динамического списка. Динамический список запрашивает данные порциями (например, первые 45 строк для отображения на экране). СУБД пытается построить наиболее быстрый план для выдачи первых строк. Однако при наличии сложных условий RLS и сортировок оптимизатор "ошибается" в плане выполнения, что приводит к полному перебору всей таблицы базы данных ради получения первых нескольких записей.
  3. Разыменование полей "через точку". Если в шаблонах RLS или в самом запросе динамического списка присутствуют обращения к реквизитам через точку (например, ТекущаяТаблица.Договор.Организация), платформа транслирует каждое такое обращение во внешнее левое соединение (LEFT JOIN). Большое количество соединений перегружает планировщик СУБД.
  4. Сортировка по представлению. Если в динамическом списке включена сортировка по ссылочному полю, для которого также рассчитывается RLS, СУБД вынуждена одновременно вычислять права и текстовые представления для сортировки всей выборки, что приводит к критическим задержкам.

---

Шаг 1. Локализация проблемы с помощью Технологического журнала

Прежде чем приступать к оптимизации кода, необходимо точно понять, какой именно SQL-запрос уходит на СУБД и сколько времени занимает его выполнение. Для этого настроим Технологический журнал (ТЖ) для сбора медленных запросов к СУБД.

Создадим файл конфигурации технологического журнала logcfg.xml со следующим содержимым:


<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
    <log location="C:\v8\logs" history="24">
        <event filter="dbmssql">
            <query duration-gt="5000"/>
            <property name="sql"/>
            <property name="context"/>
        </event>
        <event filter="dbpostgrs">
            <query duration-gt="5000"/>
            <property name="sql"/>
            <property name="context"/>
        </event>
    </log>
</config>

Этот конфигурационный файл позволит нам отлавливать все запросы к СУБД (MS SQL или PostgreSQL), время выполнения которых превышает 5 секунд. В контексте ошибки мы увидим конкретную строку модуля 1С и текст SQL-запроса, содержащий конструкции наложения RLS. Полученный запрос можно запустить в консоли СУБД (SSMS или pgAdmin) и проанализировать его план выполнения (Query Plan), чтобы увидеть, на каких соединениях или таблицах происходит сканирование.

---

Шаг 2. Оптимизация на уровне кода через Параметры сеанса

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

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

В модуле сеанса при начале работы системы пропишем следующий код:


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

После этого текст ограничения доступа RLS для защищаемой таблицы в роли упростится до тривиальной проверки вхождения поля в подготовленный массив:

ГДЕ Организация В (&ДоступныеОрганизации)

Для СУБД такой запрос будет абсолютно прозрачным. Вместо сложных соединений и условий ИЛИ оптимизатор СУБД увидит простую конструкцию IN (@P1, @P2, ...), которая отлично отрабатывает по индексу поля Организация.

---

Шаг 3. Использование производительного режима RLS в БСП

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

Проанализируем, за счет чего достигается колоссальное ускорение в этом режиме:

При этом ограничение RLS на уровне платформы заменяется простейшим и очень быстрым соединением:


ТекущаяТаблица ИЗ #ТекущаяТаблица КАК ТекущаяТаблица
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаКОбъектам КАК КлючиКОбъектам
    ПО КлючиКОбъектам.Объект = ТекущаяТаблица.Ссылка
    И КлючиКОбъектам.КлючДоступа В (
        ВЫБРАТЬ КлючиПользователя.КлючДоступа 
        ИЗ РегистрСведений.КлючиДоступаПользователей КАК КлючиПользователя 
        ГДЕ КлючиПользователя.Пользователь = &ТекущийПользователь
    )

Поскольку соединение идет по проиндексированным полям плоских таблиц, динамические списки начинают работать практически так же быстро, как и под полными правами.

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

  1. Перейдем в настройки программы (Администрирование — Настройки пользователей и прав).
  2. Найдем и включим опцию "Производительный режим ограничения доступа" (в некоторых версиях БСП этот режим включается автоматически при обновлении на новые версии конфигураций).
  3. Учитываем побочный эффект: пересчет прав теперь происходит асинхронно в фоновом режиме. При изменении прав пользователя или реквизитов объектов изменения вступают в силу с небольшой задержкой (пока фоновые задания обновляют ключи доступа в регистрах).

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


Процедура ПриЗаполненииОграниченияДоступа(Ограничение) Экспорт
    
    Ограничение.Текст =
    "РазрешитьДоступПострочно
    |УсловиеДляОрганизации(Организация)";
    
КонецПроцедуры

---

Шаг 4. Денормализация данных и избавление от точек

Если переход на производительный режим БСП по каким-то причинам невозможен, проанализируем структуру наших данных. Часто сложный шаблон RLS вынужден обращаться к реквизитам связанных объектов. Например, мы ограничиваем доступ к документу по Организации, но самой Организации в документе нет — она хранится в Договоре, ссылка на который есть в документе.

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

  1. Добавим реквизит ОрганизацияДляRLS (тип СправочникСсылка.Организации) непосредственно в шапку нашего документа.
  2. Обеспечим его автоматическое заполнение при записи документа на основании выбранного договора.
  3. Перепишем RLS, чтобы проверка шла напрямую по новому реквизиту шапки документа, без использования разыменований через точку.

Это позволит СУБД использовать индекс по полю ОрганизацияДляRLS напрямую, минуя тяжелые операции соединения таблиц (JOIN).

---

Резюме

Мы разобрали основные причины низкой производительности динамических списков при работе под RLS и методы борьбы с ними. При столкновении с зависанием списков последовательность действий должна быть следующей:

  1. Изучить структуру накладываемого RLS и отказаться от использования шаблонов с суффиксом "Расширенный" (которые содержат конструкции ИЛИ), если это возможно.
  2. Проанализировать SQL-запрос через Технологический журнал для поиска "узких мест" в плане выполнения запроса СУБД.
  3. Рассмотреть возможность кеширования прав в Параметрах сеанса в виде фиксированных массивов для простых условий.
  4. Выполнить денормализацию данных, избавившись от разыменования ссылочных полей через точку в условиях RLS.
  5. Перевести информационную базу на производительный режим ограничения доступа БСП, перейдя от динамического расчета прав к предрассчитанным ключам доступа.
← На главную