Как оптимизировать запросы с вложенными запросами под ограничение на уровне записей (RLS)?

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

Работа с механизмом ограничения доступа на уровне записей (RLS — Record Level Security) в платформе «1С:Предприятие» накладывает жесткие требования на качество и структуру написания запросов. Одной из наиболее частых проблем, с которой сталкиваются разработчики при внедрении RLS, является невозможность выполнения или катастрофическое падение производительности отчетов, содержащих сложные вложенные запросы. Для комплексного решения таких задач при переходе на новые системы доступен раздел с подбором переносов данных по тарифу PRO.

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

Почему вложенные запросы и RLS несовместимы на уровне СУБД

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

Если в исходном коде запроса используются вложенные конструкции (запросы в секции ИЗ), СУБД сталкивается со следующими трудностями:

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

Именно поэтому использование вложенных запросов при активном RLS является критической ошибкой проектирования. После исправления структуры запросов и проведения технических доработок выполняется автоматическая сверка проводок в базах 1С для контроля корректности данных.

Решение проблемы: Перенос вложенных запросов во временные таблицы

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

Проанализируем ситуацию на конкретном примере. Исходный «проблемный» запрос выглядит следующим образом:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ВложенныйЗапрос.Регистратор КАК Документ,
    ВложенныйЗапрос.Регистратор.Номер КАК НомерДокумента,
    ВложенныйЗапрос.Регистратор.Дата КАК ДатаДокумента,
    ВЫБОР КОГДА -ВложенныйЗапрос.СуммаОборот > 0 ТОГДА -ВложенныйЗапрос.СуммаОборот КОНЕЦ КАК СуммаПриход
ИЗ
    (ВЫБРАТЬ
        ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка КАК Регистратор,
        СУММА(ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Сумма) КАК СуммаОборот
    ИЗ
        Документ.ОтчетОРозничныхПродажах.ОплатаПлатежнымиКартами КАК ОтчетОРозничныхПродажахОплатаПлатежнымиКартами
    ГДЕ
        ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
        И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Проведен
        И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    СГРУППИРОВАТЬ ПО
        ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        ЧекККМОплатаПлатежнымиКартами.Ссылка,
        СУММА(ЧекККМОплатаПлатежнымиКартами.Сумма)
    ИЗ
        Документ.ЧекККМ.ОплатаПлатежнымиКартами КАК ЧекККМОплатаПлатежнымиКартами
    ГДЕ
        ЧекККМОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
        И НЕ ЧекККМОплатаПлатежнымиКартами.Ссылка.Архивный
        И ЧекККМОплатаПлатежнымиКартами.Ссылка.Проведен
        И ЧекККМОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    СГРУППИРОВАТЬ ПО
        ЧекККМОплатаПлатежнымиКартами.Ссылка
        
    // [Остальные объединения опущены для краткости...]
    ) КАК ВложенныйЗапрос

В данном случае ключевое слово РАЗРЕШЕННЫЕ стоит на самом верхнем уровне. Однако на уровне вложенного запроса выборка происходит из физических таблиц документов, к которым у пользователя ограничен доступ. Платформа не может корректно наложить условия RLS на внутреннее объединение (ОБЪЕДИНИТЬ ВСЕ) внутри вложенного запроса.

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


// ШАГ 1: Выбираем данные из физических таблиц с учетом RLS и помещаем во временную таблицу
ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка КАК Регистратор,
    СУММА(ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Сумма) КАК СуммаОборот
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    Документ.ОтчетОРозничныхПродажах.ОплатаПлатежнымиКартами КАК ОтчетОРозничныхПродажахОплатаПлатежнымиКартами
ГДЕ
    ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
    И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Проведен
    И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка

ОБЪЕДИНИТЬ ВСЕ

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

// [Остальные объединения ВТ...]
;

// ШАГ 2: Выбираем данные из уже подготовленной временной таблицы
ВЫБРАТЬ РАЗРЕШЕННЫЕ
    ВложенныйЗапрос.Регистратор КАК Документ,
    ВложенныйЗапрос.Регистратор.Номер КАК НомерДокумента,
    ВложенныйЗапрос.Регистратор.Дата КАК ДатаДокумента,
    ВЫБОР КОГДА -ВложенныйЗапрос.СуммаОборот > 0 ТОГДА -ВложенныйЗапрос.СуммаОборот КОНЕЦ КАК СуммаПриход
ИЗ
    ВТ_Данные КАК ВложенныйЗапрос

Рассмотрим, что изменилось. На первом шаге пакета мы применили конструкцию ВЫБРАТЬ РАЗРЕШЕННЫЕ и сразу отсекли все записи документов, к которым у пользователя нет доступа согласно правилам RLS. Результат объединения помещен в изолированную временную таблицу ВТ_Данные. На втором шаге мы безболезненно выбираем данные из временной таблицы без риска вызвать конфликт оптимизатора СУБД.

Особенности использования слова "РАЗРЕШЕННЫЕ" в пакетах запросов

Среди разработчиков часто возникают споры о том, где именно в пакете запросов нужно писать ключевое слово РАЗРЕШЕННЫЕ. Платформа накладывает здесь строгие синтаксические и логические правила:

Ошибка доступа даже после оптимизации запроса: разбираем причину

Бывают ситуации, когда вы переписали все вложенные запросы во временные таблицы, расставили ключевые слова РАЗРЕШЕННЫЕ, но отчет все равно аварийно завершается с ошибкой доступа (например, к справочнику «Эквайринговые терминалы»).

Выясним причину такого поведения системы. Здесь важно понимать разницу между базовыми правами доступа на уровне метаданных и ограничениями на уровне записей (RLS):

  1. Конструкция РАЗРЕШЕННЫЕ работает исключительно как «фильтр» для записей, которые пользователь в принципе имеет право читать, но некоторые из них скрыты по условиям RLS (например, по конкретной организации или складу).
  2. Если у роли пользователя в конфигураторе **вообще снята галочка «Чтение»** для определенного справочника или документа, то использование РАЗРЕШЕННЫЕ не поможет. Платформа заблокирует выполнение запроса на этапе компиляции, так как объект полностью закрыт для пользователя.

Решение проблемы: Откройте конфигуратор, найдите роль проблемного пользователя и убедитесь, что на вкладке «Права» у справочника (в нашем случае «Эквайринговые терминалы») установлены флаги «Чтение» и «Просмотр». Чтобы пользователь при этом не видел чужие элементы, настройте для этого права ограничение RLS (например, простое условие проверки по доступным организациям или филиалам). Для тестирования таких ограничений в чистой среде применяется выборочное удаление данных организаций.

Альтернативные методы решения без переписывания запросов

Если перед вами стоит задача запустить сложный самописный отчет «здесь и сейчас», а времени на переписывание сотен вложенных запросов во временные таблицы нет, можно прибегнуть к альтернативным методам обхода ограничений.

Способ 1. Использование привилегированного режима

Если отчет вызывается программно, перед выполнением запроса можно временно отключить проверку прав доступа платформы. Для этого используется метод УстановитьПривилегированныйРежим. Проанализируем пример кода:


УстановитьПривилегированныйРежим(Истина);

// Выполнение тяжелого запроса отчета
РезультатЗапроса = Запрос.Выполнить();

УстановитьPrivilegedРежим(Ложь);

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

Способ 2. Выполнение отчета СКД в привилегированном режиме

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


КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиКомпоновки);

// Инициализируем процессор компоновки с включенным привилегированным режимом
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , , Истина); // Четвертый параметр - ПривилегированныйРежим

Этот подход гарантирует, что построение отчета СКД выполнится без проверки RLS, что решит проблему падения производительности и ошибок доступа без изменения самой схемы компоновки и текста запросов.

← На главную