Как найти и устранить причину резкого замедления запросов к базе 1С на MS SQL Server?

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

В процессе промышленной эксплуатации конфигураций 1С (включая отраслевые решения с закрытыми модулями защиты) администраторы и разработчики нередко сталкиваются с ситуацией, когда стандартные операции, такие как ввод документа на основании или проведение, внезапно начинают выполняться минутами вместо долей секунды. При этом полное перестроение индексов и обновление статистики на стороне MS SQL Server возвращает былую скорость работы, но лишь временно — через несколько часов или дней тормоза повторяются. Разберем по шагам, как выявить конкретный проблемный объект в базе данных, выясним причину падения производительности и рассмотрим надежные варианты решения проблемы без необходимости регулярного перезапуска тяжелых регламентных процедур.

Диагностика зависаний с помощью трассировки в SQL Server Profiler

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

  1. Подключимся к экземпляру MS SQL Server и запустим SQL Server Profiler.
  2. Создадим новую трассировку (Trace) и выберем шаблон Blank (пустой шаблон).
  3. На вкладке Events Selection отметим события RPC:Completed (вызовы хранимых процедур и параметризованных запросов платформы) и SQL:BatchCompleted (завершение пакетов инструкций).
  4. Нажмем кнопку Column Filters и установим фильтр по полю Duration (длительность в миллисекундах): укажем значение Greater than or equal to 1000 или 2000, чтобы отсечь быстрые запросы и регистрировать только те, которые выполняются дольше 1–2 секунд.
  5. В фильтре DatabaseName укажем имя рабочей базы данных 1С, чтобы исключить влияние служебных процессов других баз.
  6. Запустим трассировку и воспроизведем медленное действие пользователя (например, ввод документа на основании).

В результате отбора мы зафиксируем конкретный параметризованный вызов через 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 с объектами метаданных 1С

В тексте полученного SQL-запроса фигурирует имя таблицы _InfoRg7405. Чтобы понять, какой именно объект конфигурации подвергается нагрузке, воспользуемся стандартным методом платформы ПолучитьСтруктуруХраненияБазыДанных(). Выполним во внешней обработке следующий код:


ТаблицаСтруктуры = ПолучитьСтруктуруХраненияБазыДанных(, Истина);
Для Каждого Стр Из ТаблицаСтруктуры Цикл
    Если Стр.ИмяТаблицыХранения = "_InfoRg7405" Тогда
        Сообщить("Имя метаданных: " + Стр.Имя);
        Сообщить("Назначение: " + Стр.Назначение);
    КонецЕсли;
КонецЦикла;

Таким образом мы определяем имя проблемного объекта (например, РегистрСведений.РегистрацияВремениРабот). Теперь разберем, почему запрос к нему исполняется за 70 секунд, а после обслуживания базы — за 250 миллисекунд.

Анализ причин деградации: Parameter Sniffing и предикаты неравенства

Проанализируем структуру запроса и механику взаимодействия платформы 1С с оптимизатором MS SQL Server:

  1. Параметрическое заглядывание (Parameter Sniffing): При первом вызове параметризованного запроса sp_executesql оптимизатор SQL Server строит план выполнения под переданные в этот момент значения (например, под пустую ссылку 0x000...). Если план скомпилирован под нетипичный набор данных, СУБД кэширует его и применяет ко всем последующим вызовам. В результате вместо эффективного точечного поиска (Index Seek) применяется полное сканирование (Index Scan).
  2. Эффект от перестроения индексов: Команды ALTER INDEX ... REBUILD или UPDATE STATISTICS принудительно очищают скомпилированные планы выполнения для затронутых таблиц из процедурного кэша. Запрос временно ускоряется не из-за физической дефрагментации, а потому что СУБД строит план заново. Как только в процедурный кэш снова попадает неоптимальный план, проблема возвращается.
  3. Условия неравенства: Конструкция T2._Fld7407RRef <> @P2 мешает прямому использованию индекса по этому полю. Если на первом месте в индексе не стоит измерение, по которому наложено строгое равенство, сервер вынужден последовательно перебирать записи таблицы.
  4. Порог обновления статистики: В MS SQL Server 2012 по умолчанию статистика автоматически пересчитывается только при модификации 20% строк плюс 500 записей. На больших регистрах сведения о распределении данных накапливают погрешность неделями.

Варианты решения на уровне MS SQL Server (без изменения кода 1С)

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

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;

Оптимизация на уровне метаданных конфигуратора 1С

Если структура регистра доступна для изменения, причину можно ликвидировать фундаментально:

  1. Порядок измерений в регистре: Кластерный индекс периодического регистра сведений строится строго по порядку измерений, заданному в дереве метаданных, плюс поле Период. Измерение, по которому в запросе накладывается жесткое условие равенства (поле _Fld7408), должно стоять первым в списке измерений регистра. Это позволит оптимизатору мгновенно позиционироваться на нужной ветке индекса.
  2. Установка свойства «Индексировать»: Для реквизитов и измерений, участвующих в группировках и условиях соединения, необходимо проверить наличие признака Индексировать (или Индексировать с доп. упорядочиванием).
  3. Свертка устаревших записей: Регистры фиксации времени выполнения работ быстро накапливают миллионы строк. Выполнение агрегатной функции MAX(Период) по историческим данным создает колоссальную нагрузку на дисковую подсистему и память. Регулярная архивация или свертка закрытых периодов позволяет поддерживать минимальный размер рабочей таблицы.
← На главную