Что вызывает мою скалярную функцию T-SQL (миллион раз)?
Пересказ статьи Matthew McGiffen. What is calling my T-SQL scalar function (millions of times)?
Здесь рассматривается метод, использующий расширенные события (XE) для идентификации родительских объектов, которые вызывают заданную функцию SQL и насколько часто.
Я работал в команде, когда выяснилось, что определенная функция выполнялась миллиарды раз на дню и - хотя одно выполнение почти не потребляло ресурсов - в целом это вызывало значительное использование CPU на сервере. Мы обсуждали способ улучшения ситуации, но это требовало изменения кода, который ее вызывал. Проблема заключалась в том, что эта функция использовалась примерно в 700 различных мест по всему коду базы данных - как в хранимых процедурах, так и в представлениях - хотя на сами представления могли затем ссылаться другие хранимые процедуры. Вместо того, чтобы обновлять весь код, разработчики хотели бы сначала выявить объекты, которые чаще всего выполняют эту функцию.
Здесь я покажу метод определения корневого вызывающего объекта в каждом случае и вычисления числа выполнений, которое было им спровоцировано. Следует заметить, что я должен захватывать событие для всех вызовов, сделанных в базе данных - т.к. я не знаю какое из них приводит к вызову функции - это означает, что, вероятно, будут накладные расходы ЦП, и если у вас происходит большое количество вызовов, это может привести к существенному использованию диска. В моем тесте сессия расширенного события потребляла около 130Мб дискового пространства для миллиона захваченных вызовов. Поэтому я планирую выполнять ее только для небольшого интервала, начиная с минуты, чтобы посмотреть, что я получаю, и предпочтительно выполнять это в тестовой среде с репрезентативной нагрузкой.
Метод использует расширенное событие module_start и функцию отслеживания причинно-следственной связи, которая позволяет отследить, как события связаны друг с другом. Давайте начнем и создадим сессию. Сначала я щелкну на Extended Events Sessions в SSMS, а затем щелкну правой кнопкой и выберу “New Session”.
На появившейся первой странице я просто дам сессии имя и установлю флажок Causality Tracking:
Затем я щелкну на событиях (events) и выберу нужное событие. В данном случае это просто событие module_start, и я не применяю никаких фильтров:
Наконец, я конфигурирую мое хранилище данных. Для этого примера я просто добавил ring_buffer (память) и event_file, допускающие хранилище до 10Гб. Когда я запускаю это в реальных условиях, мне может потребоваться немного больше места для хранения.
Теперь все сделано, и я могу щелкнуть ОК.
Теперь я могу запустить сессию XE в SSMS, выполнив щелчок правой кнопкой и выбрав “Start Session” (запустить сессию).
В моей тестовой базе данных я собираюсь теперь выполнить несколько хранимых процедур, которые выполняют мою функцию большое число раз. Идея в том, чтобы я смог затем использовать данные XE для выяснения, какая хранимая процедура вызывала ее наибольшее число раз, и чтобы затем (в реальном сценарии) я смог сосредоточиться в первую очередь на ее настройке.
Выполнив мои тесты, я могу затем остановить сессию XE и экспортировать результаты в таблицу. Я показывал этот метод в предыдущей статье.
Я экспортировал результаты в новую таблицу с именем FunctionExecution в той же базе данных. Вот структура этой таблицы:
С точки зрения анализа, интерес представляют столбцы object_name (имя объекта кода), attach_activity_id.guid (это исходит от отслеживания причинно-следственных связей и будет одинаковым для всех объектов в одном стеке вызовов) и attach_activity_id.seq (показывает порядок, в котором вызывались объекты).
Для ускорения анализа я также хочу добавить пару индексов. Столбец object_name имеет тип nvarchar(max), который не поддерживает индексирование, поэтому я сначала создаю новый столбец подходящего типа данных и копирую туда значения:
ALTER TABLE dbo.FunctionExecution ADD ObjectName sysname;
UPDATE dbo.FunctionExecution SET ObjectName = CAST([object_name] AS sysname);
Затем создаю свои индексы:
CREATE INDEX IX_ObjectName ON dbo.FunctionExecution(ObjectName) INCLUDE ([attach_activity_id.guid]);
CREATE INDEX IX_GUID ON dbo.FunctionExecution([attach_activity_id.guid]) INCLUDE (ObjectName,[attach_activity_id.seq]);
Наконец, я готов выполнить запрос для получения результатов. Я ищу имя интересующей функции, использую attach_activity_id.guid для поиска других вызовов в том же самом стеке вызовов, затем использую attach_activity_id.seq для нахождения верхнего родителя (со значением 1):
SELECT parent.ObjectName, COUNT(*) AS NumberOfExecutionsOfFunction
FROM dbo.FunctionExecution func
INNER JOIN dbo.FunctionExecution parent
ON func.[attach_activity_id.guid] = parent. [attach_activity_id.guid]
WHERE func.ObjectName = 'NonInlinable'
AND parent.[attach_activity_id.seq] = 1
GROUP BY parent.ObjectName;
Вот результаты относительно тестовых процедур, которые я выполнял при запущенной сессии XE:
Для этого примера мы можем увидеть, что хранимая процедура с именем ProcedureCallingFunctionALotOfTimes ответственна за большую часть вызовов моей функции - поэтому теоретически я сосредоточу разработку в первую очередь на ней.
Однако используйте этот метод с осторожностью, поскольку, как упоминалось в начале, возможны большие накладные расходы при захвате всех этих событий вашего сервера.
Ссылки по теме
Обратные ссылки
Автор не разрешил комментировать эту запись
Комментарии
Показывать комментарии Как список | Древовидной структурой