В процессе промышленной эксплуатации конфигураций 1С (включая отраслевые решения с закрытыми модулями защиты) администраторы и разработчики нередко сталкиваются с ситуацией, когда стандартные операции, такие как ввод документа на основании или проведение, внезапно начинают выполняться минутами вместо долей секунды. При этом полное перестроение индексов и обновление статистики на стороне MS SQL Server возвращает былую скорость работы, но лишь временно — через несколько часов или дней тормоза повторяются. Разберем по шагам, как выявить конкретный проблемный объект в базе данных, выясним причину падения производительности и рассмотрим надежные варианты решения проблемы без необходимости регулярного перезапуска тяжелых регламентных процедур.
Когда узкое место находится внутри закрытого от редактирования или чтения общего модуля, стандартный замер производительности в конфигураторе 1С оказывается малоинформативным — мы видим лишь долгий вызов защищенной процедуры. В таких ситуациях диагностику необходимо перенести на уровень СУБД. Выясним, как поймать тяжелый запрос при помощи инструмента SQL Server Profiler:
RPC:Completed (вызовы хранимых процедур и параметризованных запросов платформы) и SQL:BatchCompleted (завершение пакетов инструкций).Duration (длительность в миллисекундах): укажем значение Greater than or equal to 1000 или 2000, чтобы отсечь быстрые запросы и регистрировать только те, которые выполняются дольше 1–2 секунд.DatabaseName укажем имя рабочей базы данных 1С, чтобы исключить влияние служебных процессов других баз.В результате отбора мы зафиксируем конкретный параметризованный вызов через sp_executesql, время работы которого превышает допустимые пределы. Рассмотрим пример реального запроса, выявленного в ходе диагностики зависания:
exec sp_executesql N'SELECT
T1.Q_001_F_000RRef,
T3.Q_002_F_001RRef,
T3.Q_002_F_002_
FROM (SELECT
T2._Fld7406RRef AS Q_001_F_000RRef,
MAX(T2._Period) AS Q_001_F_001_,
T2._Fld7411 AS Q_001_F_002_
FROM _InfoRg7405 T2 WITH(NOLOCK)
WHERE (T2._Fld7408RRef = @P1) AND (T2._Fld7407RRef <> @P2)
GROUP BY T2._Fld7406RRef,
T2._Fld7411) T1
LEFT OUTER JOIN (SELECT
T4._Fld7406RRef AS Q_002_F_000RRef,
T4._Fld7407RRef AS Q_002_F_001RRef,
T4._Fld7411 AS Q_002_F_002_,
MAX(T4._Period) AS Q_002_F_003_
FROM _InfoRg7405 T4 WITH(NOLOCK)
WHERE (T4._Fld7408RRef = @P3) AND (T4._Fld7407RRef <> @P4)
GROUP BY T4._Fld7406RRef,
T4._Fld7407RRef,
T4._Fld7411) T3
ON (((T1.Q_001_F_000RRef = T3.Q_002_F_000RRef) AND (T1.Q_001_F_001_ = T3.Q_002_F_003_)) AND (T1.Q_001_F_002_ = T3.Q_002_F_002_))',
N'@P1 varbinary(16),@P2 varbinary(16),@P3 varbinary(16),@P4 varbinary(16)',
0x00000000000000000000000000000000,
0xA4A10D6407153BC548BAFB0D700F8CFA,
0x00000000000000000000000000000000,
0xA4A10D6407153BC548BAFB0D700F8CFA
В тексте полученного SQL-запроса фигурирует имя таблицы _InfoRg7405. Чтобы понять, какой именно объект конфигурации подвергается нагрузке, воспользуемся стандартным методом платформы ПолучитьСтруктуруХраненияБазыДанных(). Выполним во внешней обработке следующий код:
ТаблицаСтруктуры = ПолучитьСтруктуруХраненияБазыДанных(, Истина);
Для Каждого Стр Из ТаблицаСтруктуры Цикл
Если Стр.ИмяТаблицыХранения = "_InfoRg7405" Тогда
Сообщить("Имя метаданных: " + Стр.Имя);
Сообщить("Назначение: " + Стр.Назначение);
КонецЕсли;
КонецЦикла;
Таким образом мы определяем имя проблемного объекта (например, РегистрСведений.РегистрацияВремениРабот). Теперь разберем, почему запрос к нему исполняется за 70 секунд, а после обслуживания базы — за 250 миллисекунд.
Проанализируем структуру запроса и механику взаимодействия платформы 1С с оптимизатором MS SQL Server:
sp_executesql оптимизатор SQL Server строит план выполнения под переданные в этот момент значения (например, под пустую ссылку 0x000...). Если план скомпилирован под нетипичный набор данных, СУБД кэширует его и применяет ко всем последующим вызовам. В результате вместо эффективного точечного поиска (Index Seek) применяется полное сканирование (Index Scan).ALTER INDEX ... REBUILD или UPDATE STATISTICS принудительно очищают скомпилированные планы выполнения для затронутых таблиц из процедурного кэша. Запрос временно ускоряется не из-за физической дефрагментации, а потому что СУБД строит план заново. Как только в процедурный кэш снова попадает неоптимальный план, проблема возвращается.T2._Fld7407RRef <> @P2 мешает прямому использованию индекса по этому полю. Если на первом месте в индексе не стоит измерение, по которому наложено строгое равенство, сервер вынужден последовательно перебирать записи таблицы.Если код закрыт разработчиком конфигурации, проблему можно устранить средствами сервера баз данных.
1. Использование Plan Guide (руководство планом с принудительной рекомпиляцией)
Поскольку запрос параметризован и формируется платформой единообразно, мы можем заставить оптимизатор строить план заново при каждом выполнении именно этой инструкции с помощью хинта OPTION (RECOMPILE). Это исключит проблему Parameter Sniffing и гарантирует расчет плана под актуальные параметры без деградации скорости:
EXEC sp_create_plan_guide
@name = N'PlanGuide_Fix_InfoRg7405',
@stmt = N'SELECT
T1.Q_001_F_000RRef,
T3.Q_002_F_001RRef,
T3.Q_002_F_002_
FROM (SELECT
T2._Fld7406RRef AS Q_001_F_000RRef,
MAX(T2._Period) AS Q_001_F_001_,
T2._Fld7411 AS Q_001_F_002_
FROM _InfoRg7405 T2 WITH(NOLOCK)
WHERE (T2._Fld7408RRef = @P1) AND (T2._Fld7407RRef <> @P2)
GROUP BY T2._Fld7406RRef,
T2._Fld7411) T1
LEFT OUTER JOIN (SELECT
T4._Fld7406RRef AS Q_002_F_000RRef,
T4._Fld7407RRef AS Q_002_F_001RRef,
T4._Fld7411 AS Q_002_F_002_,
MAX(T4._Period) AS Q_002_F_003_
FROM _InfoRg7405 T4 WITH(NOLOCK)
WHERE (T4._Fld7408RRef = @P3) AND (T4._Fld7407RRef <> @P4)
GROUP BY T4._Fld7406RRef,
T4._Fld7407RRef,
T4._Fld7411) T3
ON (((T1.Q_001_F_000RRef = T3.Q_002_F_000RRef) AND (T1.Q_001_F_001_ = T3.Q_002_F_003_)) AND (T1.Q_001_F_002_ = T3.Q_002_F_002_))',
@type = N'SQL',
@module_or_batch = NULL,
@params = N'@P1 varbinary(16),@P2 varbinary(16),@P3 varbinary(16),@P4 varbinary(16)',
@hints = N'OPTION (RECOMPILE)';
2. Включение глобального флага трассировки 2371
Для версий MS SQL Server 2012 рекомендуется активировать флаг трассировки 2371. Он переключает фиксированный порог автообновления статистики (20%) на динамический: чем больше строк в таблице, тем меньший процент изменений требуется для запуска пересчета. Включим его командой:
DBCC TRACEON (2371, -1);
Для постоянного действия параметр -T2371 необходимо добавить в параметры запуска службы SQL Server (Startup Parameters).
3. Точечный сброс кэша планов вместо перестроения всей таблицы
Если требуется временное решение в качестве регламентного задания, вместо блокирующего ALTER INDEX REBUILD безопаснее очищать только кэш проблемного плана через команду DBCC FREEPROCCACHE. Найдем дескриптор скомпилированного плана запроса:
DECLARE @PlanHandle varbinary(64);
SELECT TOP 1 @PlanHandle = qs.plan_handle
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) st
WHERE st.text LIKE '%_InfoRg7405%' AND st.text LIKE '%MAX(T2._Period)%'
ORDER BY qs.last_execution_time DESC;
IF @PlanHandle IS NOT NULL
BEGIN
DBCC FREEPROCCACHE (@PlanHandle);
END;
Если структура регистра доступна для изменения, причину можно ликвидировать фундаментально:
Период. Измерение, по которому в запросе накладывается жесткое условие равенства (поле _Fld7408), должно стоять первым в списке измерений регистра. Это позволит оптимизатору мгновенно позиционироваться на нужной ветке индекса.Индексировать (или Индексировать с доп. упорядочиванием).MAX(Период) по историческим данным создает колоссальную нагрузку на дисковую подсистему и память. Регулярная архивация или свертка закрытых периодов позволяет поддерживать минимальный размер рабочей таблицы.