При администрировании и доработке крупных конфигураций на платформе 1С:Предприятие (таких как «Управление производственным предприятием 1.3» или ERP) разработчики часто сталкиваются с необходимостью оптимизации конкретных выборок. Стандартным решением кажется добавление индекса для измерения регистра накопления, по которому часто строятся отборы. Однако на практике это простое действие может привести к лавинообразному падению производительности других механизмов системы, особенно отчетов, работающих в условиях ограничения доступа на уровне записей (RLS — Row Level Security).
В этой статье мы подробно разберем, почему добавление, казалось бы, полезного индекса может замедлить работу базы данных в десятки раз, детально проанализируем физические процессы на уровне СУБД (MS SQL Server) и рассмотрим практические сценарии решения этой проблемы.
Рассмотрим классическую ситуацию. В регистре накопления ПартииТоваровНаСкладах включено индексирование по измерению СерияНоменклатуры. Для пользователей с полными правами скорость работы отчетов не изменилась или даже возросла. Однако для пользователей с ограничением прав доступа (например, с ограничением видимости по складам) те же самые отчеты начинают выполняться критически медленно.
Давайте разберем физику этого процесса на уровне MS SQL Server:
Склад обычно находится в начале этого индекса.СерияНоменклатуры, платформа создает в SQL Server новый некластеризованный индекс. Этот индекс содержит только значение серии и ссылку на физическую строку в кластеризованном индексе (RID или кластерный ключ). Важно отметить: поля Склад в этом новом индексе нет.WHERE Склад В (СписокРазрешенныхСкладов).До добавления индекса оптимизатор выбирал сканирование кластеризованного индекса (Index Scan), что приводило к последовательному чтению данных с диска. Это выполнялось в разы быстрее, чем миллионы одиночных обращений через Key Lookup.
Прежде чем вносить изменения, необходимо точно локализовать проблемный запрос и убедиться, что виной всему именно неоптимальный план СУБД. Для этого мы можем использовать два инструмента: Технологический журнал 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 по кластерному индексу таблицы регистра.
Проанализировав ситуацию, мы можем применить несколько различных подходов для нормализации работы системы.
Если отчет должен выводить информацию быстро, а пользователи, запускающие его, гарантированно должны иметь доступ к данным, мы можем временно отключить проверку прав на уровне СУБД. Для этого обработку данных и выполнение запроса необходимо перенести в привилегированный модуль.
Рассмотрим, как реализовать этот подход на примере доработки отчета на СКД:
Пример кода в привилегированном общем модуле:
// Функция выполняется на сервере в привилегированном режиме
Функция ПолучитьДанныеРегистраБезRLS(СтруктураПараметров) Экспорт
УстановитьПривилегированныйРежим(Истина);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПартииТоваров.Склад КАК Склад,
| ПартииТоваров.Номенклатура КАК Номенклатура,
| ПартииТоваров.СерияНоменклатуры КАК СерияНоменклатуры,
| ПартииТоваров.КоличествоОстаток КАК КоличествоОстаток
|ИЗ
| РегистрНакопления.ПартииТоваровНаСкладах.Остатки(&Период, СерияНоменклатуры = &Серия) КАК ПартииТоваров";
Запрос.УстановитьПараметр("Период", СтруктураПараметров.Период);
Запрос.УстановитьПараметр("Серия", СтруктураПараметров.Серия);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
В модуле отчета мы можем передать полученную таблицу значений в качестве внешнего набора данных для схемы компоновки. Таким образом, SQL-запрос выполнится без громоздких условий RLS, оптимизатор СУБД построит корректный план с использованием нашего нового индекса по сериям, и отчет сформируется мгновенно.
Если нам принципиально важно сохранить работу 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, которое будет автоматически пересоздавать данный индекс после проведения обновлений.
Если мы не хотим модифицировать метаданные в 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 связана с тем, что в шаблонах ограничений используются сложные динамические соединения с таблицами прав доступа или разыменование ссылок через точку (что порождает тяжелые конструкции LEFT JOIN).
Проанализируем шаблон RLS для склада. Если он выглядит так:
Шаблон: ГДЕ Склад В (ВЫБРАТЬ Склад ИЗ РегистрСведений.НастройкиПользователей ГДЕ Пользователь = &ПараметрТекущийПользователь)
При каждом обращении к регистру накопления СУБД будет вынуждена выполнять этот подзапрос. Для исправления ситуации рекомендуется переписать логику RLS, максимально упростив её. Отличным решением является переход на производительный режим RLS, доступный в современных версиях платформы 1С:Предприятие 8.3. В этом режиме ограничения рассчитываются предварительно и хранятся в специальных служебных таблицах ключей доступа, что сводит наложение условий RLS к простому и быстрому соединению таблиц на уровне СУБД.
Мы подробно разобрали, почему стандартное индексирование в 1С может приводить к деградации производительности при наличии ограничений RLS. Подводя итог, выделим лучшие практики для предотвращения и устранения подобных проблем:
Key Lookup и Nested Loops.