Почему добавление индекса в регистр накопления ломает отчеты с RLS и как это исправить?

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

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

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

Суть проблемы: конфликт оптимизатора СУБД и механизмов RLS

Рассмотрим классическую ситуацию. В регистре накопления ПартииТоваровНаСкладах включено индексирование по измерению СерияНоменклатуры. Для пользователей с полными правами скорость работы отчетов не изменилась или даже возросла. Однако для пользователей с ограничением прав доступа (например, с ограничением видимости по складам) те же самые отчеты начинают выполняться критически медленно.

Давайте разберем физику этого процесса на уровне MS SQL Server:

  1. Структура таблиц в СУБД: Любой регистр накопления в 1С физически представлен в базе данных несколькими таблицами (таблица движений, таблицы итогов). По умолчанию таблица движений имеет кластеризованный индекс, который включает в себя поля периода, регистратора, номера строки и измерений в порядке их следования в конфигураторе. Поле Склад обычно находится в начале этого индекса.
  2. Создание нового индекса: Когда мы устанавливаем признак «Индексировать» у измерения СерияНоменклатуры, платформа создает в SQL Server новый некластеризованный индекс. Этот индекс содержит только значение серии и ссылку на физическую строку в кластеризованном индексе (RID или кластерный ключ). Важно отметить: поля Склад в этом новом индексе нет.
  3. Как работает RLS: Механизм RLS неявно добавляет к любому запросу пользователя условия фильтрации. В нашем случае запрос дополняется условием соединения с таблицей прав или подзапросом вида WHERE Склад В (СписокРазрешенныхСкладов).
  4. Ошибка оптимизатора (Key Lookup): При формировании плана выполнения запроса оптимизатор SQL Server оценивает селективность полей. Если в запросе фигурирует отбор по серии номенклатуры, оптимизатор видит наш новый индекс по сериям и решает его использовать. СУБД быстро находит нужные серии в новом узком индексе. Однако для того, чтобы проверить условие RLS по складу, а также выбрать другие поля для отчета, СУБД вынуждена для каждой найденной строки обращаться к кластеризованному индексу. Происходит операция Key Lookup (или RID Lookup) в цикле Nested Loops. Если строк много, СУБД совершает миллионы хаотичных чтений с диска, что парализует дисковую подсистему и процессор.

До добавления индекса оптимизатор выбирал сканирование кластеризованного индекса (Index Scan), что приводило к последовательному чтению данных с диска. Это выполнялось в разы быстрее, чем миллионы одиночных обращений через Key Lookup.

Шаг 1. Локализация и диагностика проблемы

Прежде чем вносить изменения, необходимо точно локализовать проблемный запрос и убедиться, что виной всему именно неоптимальный план СУБД. Для этого мы можем использовать два инструмента: Технологический журнал 1С и SQL Server Profiler.

Настроим сбор технологического журнала для выявления медленных запросов к СУБД. Создадим файл logcfg.xml следующего содержания:


<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-journal">
    <log location="C:\v8_logs\dbms_slow" history="24">
        <event>
            <eq property="Name" value="DBMSSQL"/>
            <gt property="Duration" value="10000000"/> <!-- Запросы длительностью более 10 секунд -->
        </event>
        <property name="all"/>
    </log>
</config>

Анализируя полученные файлы логов, мы найдем точный текст SQL-запроса, отправляемого платформой 1С. В тексте запроса будут четко видны конструкции, добавленные механизмом RLS для ограничения по складам.

Далее откроем SQL Server Management Studio (SSMS), вставим полученный запрос в окно редактора и построим предполагаемый или фактический план выполнения (Estimated/Actual Execution Plan). Мы увидим графическое представление, где самым «тяжелым» узлом (с наибольшей стоимостью в процентах) будет являться связка Index Seek по нашему новому индексу серии и Key Lookup по кластерному индексу таблицы регистра.

Шаг 2. Варианты решения проблемы

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

Вариант А. Обход RLS через работу в привилегированном режиме

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

Рассмотрим, как реализовать этот подход на примере доработки отчета на СКД:

  1. Создадим общий модуль с установленным свойством Привилегированный (выполнение на сервере без проверки прав доступа).
  2. Перенесем логику формирования запроса и получения таблицы значений в этот модуль.
  3. В модуле объекта отчета перехватим событие при компоновке результата.

Пример кода в привилегированном общем модуле:


// Функция выполняется на сервере в привилегированном режиме
Функция ПолучитьДанныеРегистраБезRLS(СтруктураПараметров) Экспорт
    
    УстановитьПривилегированныйРежим(Истина);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ПартииТоваров.Склад КАК Склад,
        |   ПартииТоваров.Номенклатура КАК Номенклатура,
        |   ПартииТоваров.СерияНоменклатуры КАК СерияНоменклатуры,
        |   ПартииТоваров.КоличествоОстаток КАК КоличествоОстаток
        |ИЗ
        |   РегистрНакопления.ПартииТоваровНаСкладах.Остатки(&Период, СерияНоменклатуры = &Серия) КАК ПартииТоваров";
        
    Запрос.УстановитьПараметр("Период", СтруктураПараметров.Период);
    Запрос.УстановитьПараметр("Серия", СтруктураПараметров.Серия);
    
    Возврат Запрос.Выполнить().Выгрузить();
    
КонецФункции

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

Вариант Б. Создание покрывающего индекса на уровне SQL Server

Если нам принципиально важно сохранить работу RLS непосредственно внутри запроса отчета, стандартных средств индексирования 1С нам будет недостаточно. Платформа 1С не умеет создавать так называемые покрывающие индексы (индексы с секцией INCLUDE).

Мы можем создать такой индекс вручную непосредственно в СУБД MS SQL Server. Покрывающий индекс будет содержать в качестве ключевого поля Серию номенклатуры, а в секцию включенных (неключевых) полей мы добавим Склад и другие часто выбираемые ресурсы регистра.

Рассмотрим пример T-SQL запроса для создания такого индекса:


-- Перед выполнением определите точное имя таблицы регистра в СУБД (например, _AccumReg12345)
-- и имена колонок (например, _Fld123RRef для склада и _Fld124RRef для серии)
CREATE NONCLUSTERED INDEX [IX_Custom_AccumReg12345_Series_Covering]
ON [dbo].[_AccumReg12345] ([_Fld124RRef]) -- Ключевое поле (Серия)
INCLUDE ([_Fld123RRef], [_Fld125]) -- Покрываемые поля (Склад, Количество)
WITH (FILLFACTOR = 80, ONLINE = ON);

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

Вариант В. Использование Plan Guides (Руководств планов) в SQL Server

Если мы не хотим модифицировать метаданные в 1С и не можем создавать кастомные индексы, мы можем принудительно заставить оптимизатор MS SQL Server игнорировать созданный нами индекс серии номенклатуры для конкретного тяжелого запроса.

Для этого в СУБД применяется механизм Plan Guides. Мы фиксируем текст SQL-запроса, который генерирует платформа 1С, и создаем для него руководство, указывающее использовать старый кластеризованный индекс таблицы:


EXEC sp_create_plan_guide   
    @name = N'Guide_Fix_RLS_Parties',  
    @stmt = N'СЮДА_ВСТАВЛЯЕМ_ПОЛНЫЙ_SQL_ТЕКСТ_ЗАПРОСА_ИЗ_ТЖ',  
    @type = N'SQL',  
    @module_or_batch = NULL,  
    @params = N'@P1 nvarchar(max), @P2 ...', -- Параметры запроса  
    @hints = N'OPTION (TABLE HINT (dbo._AccumReg12345, INDEX(1)))'; -- Указание использовать только кластеризованный индекс

Этот метод является достаточно сложным в сопровождении, так как при изменении релиза платформы или незначительном изменении настроек отчета текст SQL-запроса может измениться, и руководство плана перестанет применяться.

Вариант Г. Оптимизация шаблонов RLS (Перенос в физические таблицы)

Часто медленная работа RLS связана с тем, что в шаблонах ограничений используются сложные динамические соединения с таблицами прав доступа или разыменование ссылок через точку (что порождает тяжелые конструкции LEFT JOIN).

Проанализируем шаблон RLS для склада. Если он выглядит так:


Шаблон: ГДЕ Склад В (ВЫБРАТЬ Склад ИЗ РегистрСведений.НастройкиПользователей ГДЕ Пользователь = &ПараметрТекущийПользователь)

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

Резюме и рекомендации

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

← На главную