Как оптимизировать ограничение доступа на уровне записей (RLS) по сотрудникам и подразделениям в 1С 8.3 при работе с большими объемами данных?

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

При работе с информационными системами 1С:Предприятие, особенно в условиях крупных компаний с десятками тысяч сотрудников и подразделений, часто возникает задача ограничения доступа пользователей к данным. Если пользователи должны видеть только документы, связанные с определенными подразделениями и сотрудниками, а число таких доступных объектов достигает десятков тысяч, традиционные подходы к реализации RLS (Row-Level Security) могут приводить к значительным задержкам и медленной работе системы. Давайте рассмотрим, как решить эту проблему, используя методы материализации и оптимизации запросов, даже без полноценного использования стандартной библиотеки подсистем (БСП).

Представим типовую ситуацию: в вашей базе ведется учет по нескольким десяткам тысяч сотрудников и подразделений. Есть документы, содержащие реквизит Сотрудник, и регистр сведений, по которому определяется текущее подразделение сотрудника. Задача состоит в том, чтобы ограничить доступ пользователей к этим документам, показывая только те, которые относятся к сотрудникам из разрешенных подразделений. При этом список доступных подразделений для одного пользователя может достигать 10 тысяч, а соответствующих им сотрудников — до 30 тысяч.

Начальные попытки решения этой задачи, как правило, включают:

  1. Ограничение через запрос динамического списка, напрямую фильтруя по списку доступных подразделений или сотрудников.
  2. Использование RLS, передавая в него список сотрудников или подразделений и выполняя расчет внутри запроса ограничения.
  3. Применение вспомогательных регистров для хранения связей, но без должной оптимизации.

Все эти подходы могут приводить к неприемлемому времени открытия списков документов, исчисляемому минутами, даже при, казалось бы, "удачных" вариантах.

В чем причина низкой производительности?

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

Рассмотрим пример запроса, который был предложен в ходе обсуждения как "более или менее норм" (хотя и работал минуту):


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

Этот запрос пытается в подзапросе определить разрешенных сотрудников, а затем соединить результат с основным документом. Проблема здесь в том, что СУБД может испытывать трудности с оптимизацией такого сложного подзапроса, особенно если параметры &ДоступныеУволенныеСотрудники и &МассивРазрешенныхПодразделений содержат большое количество значений. Каждый раз система вынуждена выполнять это вычисление.

Ключевые принципы оптимизации RLS

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

  1. Материализация данных: Вместо того чтобы вычислять разрешенные объекты "на лету" при каждом запросе, мы предварительно рассчитываем их и храним в отдельной, оптимизированной для чтения структуре данных (например, в регистре сведений).
  2. Индексирование: Все поля, участвующие в условиях отбора и соединениях, должны быть проиндексированы. Это позволяет СУБД быстро находить нужные данные, минуя полный перебор таблиц.
  3. Оптимизация запросов: Избегание сложных подзапросов, использование временных таблиц, правильные типы соединений и анализ планов запросов СУБД.
  4. Кэширование: Для часто используемых, но редко меняющихся данных (например, списков разрешенных организаций для пользователя), можно использовать кэширование в параметрах сеанса.

Решение: Материализация списка доступных сотрудников

Самым эффективным подходом, который показал отличные результаты в обсуждаемой ситуации, является материализация списка доступных сотрудников. Суть метода заключается в том, что для каждого пользователя мы заранее вычисляем полный список сотрудников, к данным которых он имеет доступ. Этот список сохраняется в специальном вспомогательном регистре сведений.

Шаг 1: Создание вспомогательного регистра сведений

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

Для этого регистра необходимо создать индексы по обоим измерениям, а также составной индекс Пользователь, ДоступныйСотрудник, чтобы обеспечить быстрый поиск по паре "пользователь – сотрудник".

Шаг 2: Механизм заполнения регистра

Заполнение этого регистра должно производиться регламентным заданием или по подписке на события при изменении данных, влияющих на права доступа (например, изменение подразделения сотрудника, изменение настроек прав пользователя). Это гарантирует актуальность данных в регистре.

Процедура заполнения будет примерно следующей:

  1. Для каждого пользователя, у которого есть ограничения по подразделениям:
  2. Определяем список доступных ему подразделений.
  3. На основании этого списка (и, возможно, других условий, таких как &ТекущаяДатаДень МЕЖДУ СотрудникиПредприятия_Интервалы.НачалоПериода И СотрудникиПредприятия_Интервалы.КонецПериода), вычисляем список сотрудников, работающих в этих подразделениях.
  4. Очищаем записи регистра РегистрСведений.ДоступныеСотрудники для данного пользователя.
  5. Записываем новые пары Пользователь - Сотрудник в регистр.

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

Шаг 3: Использование материализованных данных в RLS или запросе динамического списка

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

Пример оптимизированного запроса из обсуждения, который отлично сработал:


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

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

Для дальнейшей оптимизации, некоторые эксперты рекомендуют использовать внутреннее соединение (ВНУТРЕННЕЕ СОЕДИНЕНИЕ) вместо оператора В (подзапрос). Это может быть более эффективно для СУБД, так как позволяет ей лучше оптимизировать порядок выполнения операций. Запрос мог бы выглядеть так:


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

Обратите внимание, что условие (НЕ &ЕстьОграничениеПоПодразделениям ИЛИ ВыдачаНоменклатурыСотрудникам.Сотрудник = ЗНАЧЕНИЕ(Справочник.Сотрудники.ПустаяСсылка), если оно действительно необходимо, может усложнить внутреннее соединение, поэтому его нужно будет адаптировать или рассмотреть как отдельный сценарий.

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

Дополнительные рекомендации по оптимизации RLS и производительности

1. Анализ планов запросов и актуальная статистика

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

2. Индексирование полей

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

3. Использование временных таблиц вместо подзапросов

Фирма "1С" не рекомендует использовать вложенные запросы без особой необходимости и предлагает заменять их временными таблицами или соединениями. Вложенные запросы могут приводить к тому, что оптимизатор СУБД не всегда может правильно определить размер выборки вложенного запроса и построить оптимальный план обращений к физическим таблицам. Перенос обращения к виртуальной таблице в отдельный запрос с сохранением результатов во временной таблице также может улучшить производительность. Временные таблицы можно использовать многократно в пакетном запросе и передавать через МенеджерВременныхТаблиц в другие запросы, что повышает читаемость кода и обеспечивает независимость от СУБД.

4. Оптимизация соединений

Рекомендуется соединять друг с другом только объекты метаданных или временные таблицы, избегая соединений с подзапросами в условии соединения. Как мы видели выше, прямое соединение с материализованным регистром сведений часто оказывается эффективнее, чем подзапрос в условии ГДЕ ... В (...).

5. Кэширование в параметрах сеанса

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

6. Платформенный механизм оптимизации RLS

Начиная с версии платформы 8.3.18, в 1С появился механизм оптимизации RLS, который позволяет заменять подзапросы, не зависящие от защищаемой таблицы, на результат их работы. Этот результат кэшируется в оперативной памяти рабочего процесса. Учитывая, что в нашей задаче используется платформа 8.3.18, этот механизм потенциально мог бы быть полезен для определенных подзапросов. Однако, материализация, как мы выяснили, дает более контролируемый и значительный эффект, особенно для самых сложных случаев.

7. Альтернативные подходы к управлению доступом (без БСП или с ее минимальным использованием)

  1. Производительный режим RLS (концепция): Хотя в данной ситуации использование БСП ограничено, мы можем перенять принципы производительного режима. В нем отборы и ограничения не добавляются в запрос, раздувая его, а предварительно рассчитываются в "ключи доступа" (что по сути и есть наша материализация в регистре сведений).
  2. "Атомарные" роли: Рекомендуется создавать "атомарные" роли, дающие права на элементарные функции программы, из которых затем формируются профили пользователей. Это облегчает управление правами и может помочь избежать конфликтов и избыточных условий в RLS-запросах, которые возникают при пересечении нескольких ролей.
  3. Одна роль с ограничениями: Если пользователю назначено несколько ролей с разными RLS для одного и того же объекта, то условия суммируются по логическому "ИЛИ", что может замедлять выполнение запроса. Рекомендуется для настройки доступа к ограничиваемым объектам использовать одну роль с установленными ограничениями доступа, чтобы система не формировала избыточно сложные запросы.
  4. Ограничение доступа по группам: Вместо индивидуальной настройки прав для каждого пользователя, рекомендуется строить группы доступа исходя из структуры подразделений или функциональных ролей сотрудников. Это упрощает администрирование и снижает риск ошибок. В нашем примере из обсуждения используется подход с ограничением по подразделениям, что соответствует этой рекомендации.

8. Мониторинг и комплексный подход

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

Разница между RLS и отборами в динамических списках

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

Заключение

Применение метода материализации данных, то есть предварительного расчета и хранения списка разрешенных объектов в специализированном регистре сведений, в сочетании с правильным индексированием и оптимизацией запросов, позволяет добиться значительного прироста производительности при работе с ограничениями доступа на уровне записей в 1С:Предприятие 8.3. Этот подход доказал свою эффективность на практике и позволяет решать задачи, которые казались невыполнимыми при стандартных методах.

← На главную