Приветствуем вас, коллеги! Сегодня мы детально разберем крайне интересную и частую проблему, с которой сталкиваются разработчики при обновлении старых конфигураций 1С или при изменении режима совместимости платформы. Представьте ситуацию: у вас есть отлично работающая конфигурация, например, старая редакция «Управление торговлей» редакции 10.2 или аналогичная самописная система, изначально созданная под платформу «1С:Предприятие 8.1». В процессе планомерной модернизации вы переводите конфигурацию на платформу 8.2 или 8.3 и, наконец, отключаете режим совместимости с версией 8.1, установив более актуальный режим (например, «Версия 8.2.13» или выше).
Казалось бы, всё должно работать быстрее и стабильнее. Но неожиданно пользователи начинают жаловаться: при открытии справочника (например, справочника контрагентов или внутренней документации) список оказывается абсолютно пустым! Даже группы (папки) справочника бесследно исчезают из иерархического списка, хотя права на чтение групп явно разрешены в условиях ограничения доступа на уровне записей (RLS). Давайте вместе разберем по шагам, почему так происходит, заглянем «под капот» платформы и СУБД, а затем найдем надежные способы решения этой проблемы.
Проанализируем типичный код RLS, который использовался в конфигурации до отключения режима совместимости. Ограничение доступа к справочнику ДокументацияПоСистеме было настроено следующим образом:
#Если &ИспользоватьОграниченияПравДоступаККонтрагентамНаУровнеЗаписей
#Тогда
ДокументацияПоСистеме
ГДЕ
ВЫБОР
КОГДА &ЕстьПолныеПрава ИЛИ ДокументацияПоСистеме.ЭтоГруппа
ТОГДА ИСТИНА
ИНАЧЕ ВЫБОР
КОГДА НЕ ДокументацияПоСистеме.ЕстьОграничениеПоДоступу
ТОГДА ИСТИНА
КОГДА &ОтделПользователя = ДокументацияПоСистеме.ГруппыДоступа.ГруппаПользователей
ТОГДА ИСТИНА
КОГДА &ТекущийПользователь = ДокументацияПоСистеме.ГруппыДоступа.ГруппаПользователей
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
КОНЕЦ
#КонецЕсли
Давайте внимательно посмотрим на логику этого запроса. Разработчик заложил здесь абсолютно логичное правило: если элемент справочника является группой (ЭтоГруппа = ИСТИНА), то условие RLS должно сразу возвращать ИСТИНА, минуя проверку прав для конкретных пользователей. Это необходимо для того, чтобы пользователи могли видеть структуру каталогов (дерево групп) в иерархом списке и переходить по папкам, даже если у них нет доступа к конкретным элементам внутри этих папок.
Однако на практике после изменения режима совместимости на «Версия 8.2.13» и выше пользователи перестают видеть вообще что-либо. Справочник открывается абсолютно чистым. Почему же условие КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА перестало работать?
Главный секрет кроется в том, как именно платформа «1С:Предприятие» транслирует условия RLS в реальные SQL-запросы к СУБД (MS SQL Server, PostgreSQL и др.). Обратите внимание на строки, где происходит обращение к табличной части через точку:
&ОтделПользователя = ДокументацияПоСистеме.ГруппыДоступа.ГруппаПользователей
Здесь ГруппыДоступа — это табличная часть нашего справочника, а ГруппаПользователей — реквизит этой табличной части. Мы обращаемся к реквизиту табличной части «через точку» от основной таблицы справочника.
Посмотрим, как обрабатывала это обращение платформа 1С в разных режимах совместимости:
При генерации итогового SQL-запроса платформа 1С выполняла левое внешнее соединение (LEFT JOIN) основной таблицы справочника с таблицей его табличной части. Левое соединение, как мы знаем, возвращает абсолютно все записи из левой (основной) таблицы, даже если для них нет соответствующих записей в правой таблице (табличной части). Поскольку у групп (папок) табличная часть всегда пуста по определению, СУБД возвращала строки групп, заполняя поля табличной части значениями NULL. Затем вступало в силу условие ЭтоГруппа = ИСТИНА, и платформа успешно выводила группы пользователю.
В целях оптимизации производительности разработчики платформы 1С изменили логику трансляции запросов. Прямое обращение через точку к реквизитам табличных частей в условиях RLS стало транслироваться во внутреннее соединение (INNER JOIN). Внутреннее соединение возвращает только те строки, которые гарантированно присутствуют в обеих связываемых таблицах. И вот тут кроется ловушка: так как у групп (папок) табличная часть ГруппыДоступа пуста, СУБД полностью отсекает эти строки еще на этапе выполнения соединения — до того, как платформа успеет проанализировать и вычислить условие ВЫБОР КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА. Группы просто не попадают в результирующую выборку СУБД, и дерево каталогов «схлопывается».
Чтобы окончательно убедиться в этой гипотезе, проведем эксперимент. Запустим консоль запросов под пользователем с ограниченными правами (или сымитируем параметры сеанса) и выполним запрос выборки разрешенных записей:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ДокументацияПоСистеме.Ссылка КАК Ссылка,
ДокументацияПоСистеме.ЭтоГруппа КАК ЭтоГруппа
ИЗ
Справочник.ДокументацияПоСистеме КАК ДокументацияПоСистеме
Если мы используем старый вариант RLS с обращением через точку, СУБД вернет пустой результат. Как только мы уберем из условий RLS строки с обращением к табличной части через точку (ДокументацияПоСистеме.ГруппыДоступа...), все группы мгновенно появятся на экране! Это наглядно доказывает пагубное влияние неявного INNER JOIN.
Выяснив причину, найдем правильный способ обхода этого поведения. Чтобы заставить СУБД не отсекать группы из-за пустоты табличной части, нам необходимо избавиться от неявного внутреннего соединения. Для этого мы заменим обращение через точку на вложенный запрос с использованием конструкции В (ВЫБРАТЬ...).
Давайте перепишем наше условие RLS следующим образом:
#Если &ИспользоватьОграниченияПравДоступаККонтрагентамНаУровнеЗаписей
#Тогда
ДокументацияПоСистеме
ГДЕ
ВЫБОР
КОГДА &ЕстьПолныеПрава ТОГДА ИСТИНА
КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА
ИНАЧЕ ВЫБОР
КОГДА НЕ ДокументацияПоСистеме.ЕстьОграничениеПоДоступу ТОГДА ИСТИНА
КОГДА ДокументацияПоСистеме.Ссылка В (
ВЫБРАТЬ
ДокументацияПоСистемеГруппыДоступа.Ссылка
ИЗ
Справочник.ДокументацияПоСистеме.ГруппыДоступа КАК ДокументацияПоСистемеГруппыДоступа
ГДЕ
(ДокументацияПоСистемеГруппыДоступа.ГруппаПользователей = &ОтделПользователя
ИЛИ ДокументацияПоСистемеГруппыДоступа.ГруппаПользователей = &ТекущийПользователь)
) ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
КОНЕЦ
#КонецЕсли
Разберем, почему эта конструкция работает безупречно:
INNER JOIN с табличной частью. СУБД выбирает записи справочника независимо.КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА. На этом вычисление логического выражения для данной строки завершается. Тяжелый вложенный запрос к табличной части для групп даже не начинает выполняться, что значительно экономит ресурсы СУБД.Хотя предложенное выше решение полностью решает проблему «здесь и сейчас», использование табличных частей непосредственно в условиях RLS считается не лучшей практикой в современной разработке на платформе 1С. Рассмотрим основные причины и альтернативные, более производительные архитектурные подходы.
1. Проблема производительности при росте базы данных
Каждый раз, когда пользователь открывает список справочника, строит отчет или выбирает элемент в документе, платформа неявно добавляет наши условия RLS к основному тексту запроса. Если в справочнике десятки тысяч элементов, постоянное выполнение вложенных запросов к табличным частям приведет к колоссальной нагрузке на СУБД, блокировкам таблиц и существенному замедлению работы интерфейса.
2. Использование регистров сведений для хранения прав доступа
Рекомендуется выносить права доступа из табличных частей объектов в плоские, хорошо индексированные регистры сведений. Создадим, к примеру, регистр сведений ПраваДоступаКОбъектам со следующей структурой:
ОбъектДоступа (тип: СправочникСсылка.ДокументацияПоСистеме)ПользовательИлиГруппа (тип: СправочникСсылка.Пользователи, СправочникСсылка.ГруппыПользователей)При записи элемента справочника мы программно заполняем этот регистр сведений. В этом случае наш запрос RLS станет значительно проще, лаконичнее и будет выполняться в разы быстрее благодаря индексам регистра сведений:
#Если &ИспользоватьОграниченияПравДоступаККонтрагентамНаУровнеЗаписей
#Тогда
ДокументацияПоСистеме
ГДЕ
ВЫБОР
КОГДА &ЕстьПолныеПрава ТОГДА ИСТИНА
КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА
ИНАЧЕ ВЫБОР
КОГДА НЕ ДокументацияПоСистеме.ЕстьОграничениеПоДоступу ТОГДА ИСТИНА
КОГДА ДокументацияПоСистеме.Ссылка В (
ВЫБРАТЬ
РегистрПрава.ОбъектДоступа
ИЗ
РегистрСведений.ПраваДоступаКОбъектам КАК РегистрПрава
ГДЕ
РегистрПрава.ПользовательИлиГруппа В (&ОтделПользователя, &ТекущийПользователь)
) ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ
КОНЕЦ
#КонецЕсли
3. Переход на производительный режим RLS (БСП 3.x)
Если вы планируете долгосрочное развитие конфигурации и перевод ее на управляемые формы, настоятельно рекомендуем использовать механизмы Библиотеки Стандартных Подсистем (БСП), а именно подсистему «Управление доступом».
В современных версиях БСП реализован так называемый производительный режим работы RLS. Вместо динамического расчета прав при каждом запросе система рассчитывает права один раз (при записи объекта или изменении настроек прав) и сохраняет готовые ключи доступа в кэш-таблицы (специальные регистры). Запросы RLS в БСП оптимизированы на уровне платформы, автоматически учитывают иерархию справочников и полностью защищены от подобных ошибок с неявными соединениями таблиц.
Мы надеемся, что данный детальный разбор помог вам глубже понять тонкости работы RLS в платформе 1С и успешно решить проблему с видимостью групп справочников в вашей системе. Желаем вам стабильного кода и быстрой работы ваших баз данных!