Работа с механизмом ограничения доступа на уровне записей (RLS — Record Level Security) в платформе «1С:Предприятие» накладывает жесткие требования на качество и структуру написания запросов. Одной из наиболее частых проблем, с которой сталкиваются разработчики при внедрении RLS, является невозможность выполнения или катастрофическое падение производительности отчетов, содержащих сложные вложенные запросы. Для комплексного решения таких задач при переходе на новые системы доступен раздел с подбором переносов данных по тарифу PRO.
В этой статье мы подробно разберем, почему вложенные запросы конфликтуют с RLS, как правильно переписать такой запрос с использованием пакетов и временных таблиц, а также рассмотрим сопутствующие проблемы с правами доступа к метаданным.
Для того чтобы понять суть проблемы, нам необходимо заглянуть под капот платформы «1С:Предприятие». Когда пользователь с ограниченными правами запускает отчет, платформа перехватывает исходный запрос и «на лету» модифицирует его перед отправкой в СУБД (MS SQL, PostgreSQL и др.). В тело запроса встраиваются дополнительные условия ограничения, прописанные в роли пользователя.
Если в исходном коде запроса используются вложенные конструкции (запросы в секции ИЗ), СУБД сталкивается со следующими трудностями:
Именно поэтому использование вложенных запросов при активном RLS является критической ошибкой проектирования. После исправления структуры запросов и проведения технических доработок выполняется автоматическая сверка проводок в базах 1С для контроля корректности данных.
Единственным правильным техническим решением является полный отказ от вложенных запросов в пользу пакетных запросов с использованием временных таблиц. Временные таблицы физически создаются в СУБД (в базе данных tempdb) и изолируют промежуточные данные. На уровне временных таблиц RLS уже не применяется, так как это внутренние структуры сеанса, не связанные напрямую с объектами метаданных конфигурации.
Проанализируем ситуацию на конкретном примере. Исходный «проблемный» запрос выглядит следующим образом:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВложенныйЗапрос.Регистратор КАК Документ,
ВложенныйЗапрос.Регистратор.Номер КАК НомерДокумента,
ВложенныйЗапрос.Регистратор.Дата КАК ДатаДокумента,
ВЫБОР КОГДА -ВложенныйЗапрос.СуммаОборот > 0 ТОГДА -ВложенныйЗапрос.СуммаОборот КОНЕЦ КАК СуммаПриход
ИЗ
(ВЫБРАТЬ
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка КАК Регистратор,
СУММА(ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Сумма) КАК СуммаОборот
ИЗ
Документ.ОтчетОРозничныхПродажах.ОплатаПлатежнымиКартами КАК ОтчетОРозничныхПродажахОплатаПлатежнымиКартами
ГДЕ
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Проведен
И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЧекККМОплатаПлатежнымиКартами.Ссылка,
СУММА(ЧекККМОплатаПлатежнымиКартами.Сумма)
ИЗ
Документ.ЧекККМ.ОплатаПлатежнымиКартами КАК ЧекККМОплатаПлатежнымиКартами
ГДЕ
ЧекККМОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
И НЕ ЧекККМОплатаПлатежнымиКартами.Ссылка.Архивный
И ЧекККМОплатаПлатежнымиКартами.Ссылка.Проведен
И ЧекККМОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ЧекККМОплатаПлатежнымиКартами.Ссылка
// [Остальные объединения опущены для краткости...]
) КАК ВложенныйЗапрос
В данном случае ключевое слово РАЗРЕШЕННЫЕ стоит на самом верхнем уровне. Однако на уровне вложенного запроса выборка происходит из физических таблиц документов, к которым у пользователя ограничен доступ. Платформа не может корректно наложить условия RLS на внутреннее объединение (ОБЪЕДИНИТЬ ВСЕ) внутри вложенного запроса.
Давайте перепишем этот запрос по шагам, выделив промежуточные данные в пакет запросов с созданием временной таблицы:
// ШАГ 1: Выбираем данные из физических таблиц с учетом RLS и помещаем во временную таблицу
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка КАК Регистратор,
СУММА(ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Сумма) КАК СуммаОборот
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Документ.ОтчетОРозничныхПродажах.ОплатаПлатежнымиКартами КАК ОтчетОРозничныхПродажахОплатаПлатежнымиКартами
ГДЕ
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Проведен
И ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ОтчетОРозничныхПродажахОплатаПлатежнымиКартами.Ссылка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ЧекККМОплатаПлатежнымиКартами.Ссылка,
СУММА(ЧекККМОплатаПлатежнымиКартами.Сумма)
ИЗ
Документ.ЧекККМ.ОплатаПлатежнымиКартами КАК ЧекККМОплатаПлатежнымиКартами
ГДЕ
ЧекККМОплатаПлатежнымиКартами.Ссылка.Склад = &Склад
И НЕ ЧекККМОплатаПлатежнымиКартами.Ссылка.Архивный
И ЧекККМОплатаПлатежнымиКартами.Ссылка.Проведен
И ЧекККМОплатаПлатежнымиКартами.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ЧекККМОплатаПлатежнымиКартами.Ссылка
// [Остальные объединения ВТ...]
;
// ШАГ 2: Выбираем данные из уже подготовленной временной таблицы
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ВложенныйЗапрос.Регистратор КАК Документ,
ВложенныйЗапрос.Регистратор.Номер КАК НомерДокумента,
ВложенныйЗапрос.Регистратор.Дата КАК ДатаДокумента,
ВЫБОР КОГДА -ВложенныйЗапрос.СуммаОборот > 0 ТОГДА -ВложенныйЗапрос.СуммаОборот КОНЕЦ КАК СуммаПриход
ИЗ
ВТ_Данные КАК ВложенныйЗапрос
Рассмотрим, что изменилось. На первом шаге пакета мы применили конструкцию ВЫБРАТЬ РАЗРЕШЕННЫЕ и сразу отсекли все записи документов, к которым у пользователя нет доступа согласно правилам RLS. Результат объединения помещен в изолированную временную таблицу ВТ_Данные. На втором шаге мы безболезненно выбираем данные из временной таблицы без риска вызвать конфликт оптимизатора СУБД.
Среди разработчиков часто возникают споры о том, где именно в пакете запросов нужно писать ключевое слово РАЗРЕШЕННЫЕ. Платформа накладывает здесь строгие синтаксические и логические правила:
РАЗРЕШЕННЫЕ должно быть указано в каждом запросе пакета, который обращается к реальным (физическим) таблицам базы данных, содержащим ограничения доступа.ВТ_Данные на втором шаге) указывать РАЗРЕШЕННЫЕ не обязательно, так как на уровне СУБД эта таблица уже не содержит метаданных 1С и к ней неприменимы правила RLS. Тем не менее, указание этого ключевого слова в финальном запросе пакета является хорошим тоном для обеспечения совместимости.Бывают ситуации, когда вы переписали все вложенные запросы во временные таблицы, расставили ключевые слова РАЗРЕШЕННЫЕ, но отчет все равно аварийно завершается с ошибкой доступа (например, к справочнику «Эквайринговые терминалы»).
Выясним причину такого поведения системы. Здесь важно понимать разницу между базовыми правами доступа на уровне метаданных и ограничениями на уровне записей (RLS):
РАЗРЕШЕННЫЕ работает исключительно как «фильтр» для записей, которые пользователь в принципе имеет право читать, но некоторые из них скрыты по условиям RLS (например, по конкретной организации или складу).РАЗРЕШЕННЫЕ не поможет. Платформа заблокирует выполнение запроса на этапе компиляции, так как объект полностью закрыт для пользователя.Решение проблемы: Откройте конфигуратор, найдите роль проблемного пользователя и убедитесь, что на вкладке «Права» у справочника (в нашем случае «Эквайринговые терминалы») установлены флаги «Чтение» и «Просмотр». Чтобы пользователь при этом не видел чужие элементы, настройте для этого права ограничение RLS (например, простое условие проверки по доступным организациям или филиалам). Для тестирования таких ограничений в чистой среде применяется выборочное удаление данных организаций.
Если перед вами стоит задача запустить сложный самописный отчет «здесь и сейчас», а времени на переписывание сотен вложенных запросов во временные таблицы нет, можно прибегнуть к альтернативным методам обхода ограничений.
Способ 1. Использование привилегированного режима
Если отчет вызывается программно, перед выполнением запроса можно временно отключить проверку прав доступа платформы. Для этого используется метод УстановитьПривилегированныйРежим. Проанализируем пример кода:
УстановитьПривилегированныйРежим(Истина);
// Выполнение тяжелого запроса отчета
РезультатЗапроса = Запрос.Выполнить();
УстановитьPrivilegedРежим(Ложь);
Этот способ позволяет выполнить запрос на максимальной скорости напрямую к СУБД, полностью игнорируя настройки RLS. Важно: применяйте этот метод с осторожностью. Убедитесь, что отчет выводит данные только в разрешенном для пользователя контексте, чтобы избежать утечки конфиденциальной информации.
Способ 2. Выполнение отчета СКД в привилегированном режиме
Если ваш отчет построен на базе Системы компоновки данных (СКД), вы можете настроить запуск схемы компоновки в привилегированном режиме. Для этого при инициализации процессора компоновки данных укажите соответствующий параметр:
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, НастройкиКомпоновки);
// Инициализируем процессор компоновки с включенным привилегированным режимом
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , , Истина); // Четвертый параметр - ПривилегированныйРежим
Этот подход гарантирует, что построение отчета СКД выполнится без проверки RLS, что решит проблему падения производительности и ошибок доступа без изменения самой схемы компоновки и текста запросов.