Почему пропадают группы справочника при включенном RLS после отключения режима совместимости 8.1?

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

Приветствуем вас, коллеги! Сегодня мы детально разберем крайне интересную и частую проблему, с которой сталкиваются разработчики при обновлении старых конфигураций 1С или при изменении режима совместимости платформы. Представьте ситуацию: у вас есть отлично работающая конфигурация, например, старая редакция «Управление торговлей» редакции 10.2 или аналогичная самописная система, изначально созданная под платформу «1С:Предприятие 8.1». В процессе планомерной модернизации вы переводите конфигурацию на платформу 8.2 или 8.3 и, наконец, отключаете режим совместимости с версией 8.1, установив более актуальный режим (например, «Версия 8.2.13» или выше).

Казалось бы, всё должно работать быстрее и стабильнее. Но неожиданно пользователи начинают жаловаться: при открытии справочника (например, справочника контрагентов или внутренней документации) список оказывается абсолютно пустым! Даже группы (папки) справочника бесследно исчезают из иерархического списка, хотя права на чтение групп явно разрешены в условиях ограничения доступа на уровне записей (RLS). Давайте вместе разберем по шагам, почему так происходит, заглянем «под капот» платформы и СУБД, а затем найдем надежные способы решения этой проблемы.

Анализ исходной ситуации и кода ограничения доступа

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


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

Давайте внимательно посмотрим на логику этого запроса. Разработчик заложил здесь абсолютно логичное правило: если элемент справочника является группой (ЭтоГруппа = ИСТИНА), то условие RLS должно сразу возвращать ИСТИНА, минуя проверку прав для конкретных пользователей. Это необходимо для того, чтобы пользователи могли видеть структуру каталогов (дерево групп) в иерархом списке и переходить по папкам, даже если у них нет доступа к конкретным элементам внутри этих папок.

Однако на практике после изменения режима совместимости на «Версия 8.2.13» и выше пользователи перестают видеть вообще что-либо. Справочник открывается абсолютно чистым. Почему же условие КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА перестало работать?

В чем техническая причина? Разница в трансляции SQL-запросов в 8.1 и 8.2+

Главный секрет кроется в том, как именно платформа «1С:Предприятие» транслирует условия RLS в реальные SQL-запросы к СУБД (MS SQL Server, PostgreSQL и др.). Обратите внимание на строки, где происходит обращение к табличной части через точку:

&ОтделПользователя = ДокументацияПоСистеме.ГруппыДоступа.ГруппаПользователей

Здесь ГруппыДоступа — это табличная часть нашего справочника, а ГруппаПользователей — реквизит этой табличной части. Мы обращаемся к реквизиту табличной части «через точку» от основной таблицы справочника.

Посмотрим, как обрабатывала это обращение платформа 1С в разных режимах совместимости:

  1. Режим совместимости «Версия 8.1» (или запуск на платформе 8.1):

    При генерации итогового SQL-запроса платформа 1С выполняла левое внешнее соединение (LEFT JOIN) основной таблицы справочника с таблицей его табличной части. Левое соединение, как мы знаем, возвращает абсолютно все записи из левой (основной) таблицы, даже если для них нет соответствующих записей в правой таблице (табличной части). Поскольку у групп (папок) табличная часть всегда пуста по определению, СУБД возвращала строки групп, заполняя поля табличной части значениями NULL. Затем вступало в силу условие ЭтоГруппа = ИСТИНА, и платформа успешно выводила группы пользователю.

  2. Режим совместимости «Версия 8.2.13» и выше (включая платформу 8.3):

    В целях оптимизации производительности разработчики платформы 1С изменили логику трансляции запросов. Прямое обращение через точку к реквизитам табличных частей в условиях RLS стало транслироваться во внутреннее соединение (INNER JOIN). Внутреннее соединение возвращает только те строки, которые гарантированно присутствуют в обеих связываемых таблицах. И вот тут кроется ловушка: так как у групп (папок) табличная часть ГруппыДоступа пуста, СУБД полностью отсекает эти строки еще на этапе выполнения соединения — до того, как платформа успеет проанализировать и вычислить условие ВЫБОР КОГДА ДокументацияПоСистеме.ЭтоГруппа ТОГДА ИСТИНА. Группы просто не попадают в результирующую выборку СУБД, и дерево каталогов «схлопывается».

Эксперимент в консоли запросов

Чтобы окончательно убедиться в этой гипотезе, проведем эксперимент. Запустим консоль запросов под пользователем с ограниченными правами (или сымитируем параметры сеанса) и выполним запрос выборки разрешенных записей:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ДокументацияПоСистеме.Ссылка КАК Ссылка,
    ДокументацияПоСистеме.ЭтоГруппа КАК ЭтоГруппа
ИЗ
    Справочник.ДокументацияПоСистеме КАК ДокументацияПоСистеме

Если мы используем старый вариант RLS с обращением через точку, СУБД вернет пустой результат. Как только мы уберем из условий RLS строки с обращением к табличной части через точку (ДокументацияПоСистеме.ГруппыДоступа...), все группы мгновенно появятся на экране! Это наглядно доказывает пагубное влияние неявного INNER JOIN.

Решение проблемы: правильное написание запроса RLS

Выяснив причину, найдем правильный способ обхода этого поведения. Чтобы заставить СУБД не отсекать группы из-за пустоты табличной части, нам необходимо избавиться от неявного внутреннего соединения. Для этого мы заменим обращение через точку на вложенный запрос с использованием конструкции В (ВЫБРАТЬ...).

Давайте перепишем наше условие RLS следующим образом:


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

Разберем, почему эта конструкция работает безупречно:

Лучшие практики и рекомендации по оптимизации RLS

Хотя предложенное выше решение полностью решает проблему «здесь и сейчас», использование табличных частей непосредственно в условиях RLS считается не лучшей практикой в современной разработке на платформе 1С. Рассмотрим основные причины и альтернативные, более производительные архитектурные подходы.

1. Проблема производительности при росте базы данных

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

2. Использование регистров сведений для хранения прав доступа

Рекомендуется выносить права доступа из табличных частей объектов в плоские, хорошо индексированные регистры сведений. Создадим, к примеру, регистр сведений ПраваДоступаКОбъектам со следующей структурой:

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


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

3. Переход на производительный режим RLS (БСП 3.x)

Если вы планируете долгосрочное развитие конфигурации и перевод ее на управляемые формы, настоятельно рекомендуем использовать механизмы Библиотеки Стандартных Подсистем (БСП), а именно подсистему «Управление доступом».

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

Мы надеемся, что данный детальный разбор помог вам глубже понять тонкости работы RLS в платформе 1С и успешно решить проблему с видимостью групп справочников в вашей системе. Желаем вам стабильного кода и быстрой работы ваших баз данных!

← На главную