Разработчики систем на платформе 1С:Предприятие часто сталкиваются с ситуацией, когда без ограничений прав на уровне записей (RLS) система работает исключительно быстро, но при включении механизмов разграничения прав динамические списки начинают "зависать" на десятки и сотни секунд. Особенно остро эта проблема проявляется на таблицах с объемом данных более 100 000 записей.
В этой статье мы подробно разберем, почему возникает такое падение производительности, как правильно локализовать проблему с помощью инструментов платформы и СУБД, а также рассмотрим архитектурные варианты решения этой проблемы.
---
Для того чтобы понять причину зависания, необходимо проанализировать, как именно платформа 1С взаимодействует с СУБД при выводе данных в динамический список. Когда пользователь открывает форму списка, платформа отправляет в СУБД запрос, к которому динамически "пристраивается" условие ограничения прав (текст RLS).
Основными причинами падения скорости работы являются следующие факторы:
ИЛИ (OR) в условиях ограничения. Использование сложных конструкций в шаблонах, таких как #ПоЗначениямИНаборамРасширенный, приводит к генерации тяжелых SQL-запросов со множеством ветвлений. Для оптимизатора СУБД (будь то MS SQL Server или PostgreSQL) наличие ИЛИ в условиях соединения таблиц или фильтрации часто делает невозможным использование индексов. СУБД отказывается от поиска по индексу (Index Seek) и переходит к полному сканированию таблицы (Table Scan / Clustered Index Scan).ТекущаяТаблица.Договор.Организация), платформа транслирует каждое такое обращение во внешнее левое соединение (LEFT JOIN). Большое количество соединений перегружает планировщик СУБД.---
Прежде чем приступать к оптимизации кода, необходимо точно понять, какой именно 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), чтобы увидеть, на каких соединениях или таблицах происходит сканирование.
---
Если используемый алгоритм RLS относительно прост (например, пользователю разрешены только определенные склады, организации или рабочие группы), мы можем отказаться от сложных тяжелых шаблонов и переписать условие ограничения, используя предварительно заполненные параметры сеанса.
Рассмотрим этот метод подробнее. При старте сеанса пользователя мы можем один раз вычислить массив доступных ему объектов (например, разрешенных организаций) и поместить этот массив в параметр сеанса типа ФиксированныйМассив.
В модуле сеанса при начале работы системы пропишем следующий код:
Процедура УстановкаПараметровСеанса(ТребуемыеПараметры)
Если ТребуемыеПараметры.Найти("ДоступныеОрганизации") <> Неопределено Тогда
СписокОрганизаций = Новый Массив;
// Здесь реализуем логику получения разрешенных организаций для текущего пользователя
Выборка = Справочники.Организации.Выбрать();
Пока Выборка.Следующий() Цикл
Если ПроверитьДоступПользователя(ПараметрыСеанса.ТекущийПользователь, Выборка.Ссылка) Тогда
СписокОрганизаций.Добавить(Выборка.Ссылка);
КонецЕсли;
КонецЦикла;
ПараметрыСеанса.ДоступныеОрганизации = Новый ФиксированныйМассив(СписокОрганизаций);
КонецЕсли;
КонецПроцедуры
После этого текст ограничения доступа RLS для защищаемой таблицы в роли упростится до тривиальной проверки вхождения поля в подготовленный массив:
ГДЕ Организация В (&ДоступныеОрганизации)
Для СУБД такой запрос будет абсолютно прозрачным. Вместо сложных соединений и условий ИЛИ оптимизатор СУБД увидит простую конструкцию IN (@P1, @P2, ...), которая отлично отрабатывает по индексу поля Организация.
---
Если мы работаем в современной конфигурации на базе Библиотеки Стандартных Подсистем (БСП) редакции 3.0 и выше, разработчики 1С предоставляют мощный альтернативный инструмент — производительный режим ограничения доступа (универсальный RLS).
Проанализируем, за счет чего достигается колоссальное ускорение в этом режиме:
КлючиДоступа, регистры сведений КлючиДоступаКОбъектам и КлючиДоступаПользователей.Объект -> КлючДоступа.КлючиДоступаПользователей хранится плоский список разрешенных ему ключей.При этом ограничение RLS на уровне платформы заменяется простейшим и очень быстрым соединением:
ТекущаяТаблица ИЗ #ТекущаяТаблица КАК ТекущаяТаблица
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КлючиДоступаКОбъектам КАК КлючиКОбъектам
ПО КлючиКОбъектам.Объект = ТекущаяТаблица.Ссылка
И КлючиКОбъектам.КлючДоступа В (
ВЫБРАТЬ КлючиПользователя.КлючДоступа
ИЗ РегистрСведений.КлючиДоступаПользователей КАК КлючиПользователя
ГДЕ КлючиПользователя.Пользователь = &ТекущийПользователь
)
Поскольку соединение идет по проиндексированным полям плоских таблиц, динамические списки начинают работать практически так же быстро, как и под полными правами.
Чтобы задействовать этот режим, выполним следующие шаги:
Для разработчиков, создающих собственные объекты метаданных, важно правильно интегрировать их в этот механизм. В модуле менеджера вашего объекта необходимо определить процедуру заполнения ограничений доступа:
Процедура ПриЗаполненииОграниченияДоступа(Ограничение) Экспорт
Ограничение.Текст =
"РазрешитьДоступПострочно
|УсловиеДляОрганизации(Организация)";
КонецПроцедуры
---
Если переход на производительный режим БСП по каким-то причинам невозможен, проанализируем структуру наших данных. Часто сложный шаблон RLS вынужден обращаться к реквизитам связанных объектов. Например, мы ограничиваем доступ к документу по Организации, но самой Организации в документе нет — она хранится в Договоре, ссылка на который есть в документе.
В этом случае шаблон RLS транслируется в SQL с соединением таблицы Договоров, что критически снижает скорость. Для оптимизации выполним денормализацию:
ОрганизацияДляRLS (тип СправочникСсылка.Организации) непосредственно в шапку нашего документа.Это позволит СУБД использовать индекс по полю ОрганизацияДляRLS напрямую, минуя тяжелые операции соединения таблиц (JOIN).
---
Мы разобрали основные причины низкой производительности динамических списков при работе под RLS и методы борьбы с ними. При столкновении с зависанием списков последовательность действий должна быть следующей:
ИЛИ), если это возможно.