Как отслеживать вызовы функций T-SQL
-
20-08-2019 - |
Вопрос
Я пытаюсь отладить довольно сложный оценщик формул, написанный в UDF T-SQL (не спрашивайте), который рекурсивно (но косвенно через промежуточную функцию) вызывает себя, бла-бла.
И, конечно же, у нас есть ошибка.
Теперь, используя операторы PRINT (которые затем можно прочитать из ADO.NET, реализовав обработчик события InfoMessage), я могу смоделировать трассировку для хранимых процедур.
Если сделать то же самое для UDF, во время компиляции появится сообщение:
Invalid use of side-effecting or time-dependent operator in 'PRINT' within a function.
Я получаю сообщение (PRINT выполняет некоторые действия, например, сбрасывает @@ROWCOUNT
что определенно запрещено в UDF, но как я могу отслеживать вызовы?Я хочу распечатать эту трассировку, чтобы иметь возможность изучать ее, не отвлекаясь на пошаговое выполнение вызовов в отладчике...
РЕДАКТИРОВАТЬ: Я пытался использовать SQL Profiler (для меня это был первый раз), но я не могу понять, что отслеживать:Хотя я могу получить трассировку для вывода запросов, отправленных в базу данных, они непрозрачны в том смысле, что я не могу перейти к выражениям-UDF, называемым:Я могу отследить фактическую вызванную хранимую процедуру, но пользовательские функции, вызванные этой процедурой, не указаны.Я что-то пропустил?Я думаю, нет...
РЕДАКТИРОВАТЬ № 2: Хотя (автоматически) принятый ответ отслеживает вызовы функций - очень полезно, спасибо - это не помогает выяснить, какие параметры были прошедший к функции.Это, конечно, важно для отладка рекурсивные функции.Я напишу, если вообще найду решение...
Решение
Почему бы не использовать SQL Profiler с добавленными событиями уровня инструкций?
Редактировать:Добавьте события для хранимых процедур:SP: STMT Запуск или SP: STMT завершены переменные использования для отладки, если это необходимо, т.е.set @debug='я здесь';UDF, хотя и не являются технически хранимыми процедурами, будут отслеживаться с помощью событий уровня операторов.
Другие советы
В профилировщике SQL вам необходимо:SP:Запуск, SP:StmtStarting, SP:Завершено, SQL:BatchStarting.Затем вы получаете каждую запись и выход из функций/хранимых процедур.
alter FUNCTION [dbo].[ufn_mjf](@i numeric(10))
RETURNS numeric(20)
AS
BEGIN
declare @datapoint varchar(10)
set @datapoint = 'hello world'
return @i
END
go
drop table foo
go
create table dbo.foo ( foo_id numeric(10))
go
delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
при этом я получаю:
SQL:BatchStarting alter FUNCTION [dbo].[ufn_mjf](@i numeric(10))
SQL:BatchStarting drop table foo
SQL:BatchStarting create table dbo.foo ( foo_id numeric(10))
SQL:BatchStarting delete from foo
insert into foo ( foo_id ) values ( 1 )
insert into foo ( foo_id ) values ( 2 )
select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set @datapoint = 'hello world'
SP:StmtStarting return @i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:Starting select foo_id, dbo.ufn_mjf(foo_id) from foo
SP:StmtStarting set @datapoint = 'hello world'
SP:StmtStarting return @i
SP:Completed select foo_id, dbo.ufn_mjf(foo_id) from foo
тебе этого достаточно?
Этот выглядит как то, что вам нужно, но доступно только в командных/профессиональных версиях Visual Studio.
Используйте SQL Profiler, я рекомендую вам переборщить с добавлением событий в первый раз, что позволит вам почувствовать, что вам нужно.Без тестирования я бы добавил события для SP:StmtStarted (или Completed, или обоих), SQL:StmtStarted (снова Completed или обоих).
Я поддерживаю предложение SQL Profiler.Потратьте некоторое время, чтобы настроить его так, чтобы регистрировались только те события, которые вас интересуют, чтобы сократить размер вывода.Вы можете вывести трассировку в файл — я часто затем загружал этот файл обратно в таблицу, чтобы включить анализ.(чрезвычайно удобно для анализа производительности, хотя, без сомнения, кто-то скажет мне, что в 2008 году все это где-то встроено...)
Иногда у вас не будет разрешений на запуск SQL Profiler, поскольку он замедляет работу сервера — попросите своего администратора базы данных предоставить вам разрешение на вашем сервере разработки.У них не должно быть с этим проблем.
В прошлом мне приходилось брать типичные значения, которые были бы в UDF, а затем запускать только часть udf в отдельном окне запроса как прямой SQL, а не udf, используя типичные значения в качестве переменных, установленных с помощью объявления и оператора set.Если он запускается из таблицы, а не имеет только одно значение, я бы установил временную таблицу или табличную переменную с входными значениями, а затем запустил их через sql в UDF (но опять же как прямой SQL, а не UDF) через курсор.Запустив прямой SQL, вы можете использовать операторы печати, чтобы увидеть, что происходит.Я знаю, что это больно, но это работает.(Я выполняю аналогичный процесс при создании/отладке триггеров, настраиваю #inserted и #deleted с моими тестовыми значениями, а затем проверяю код, который собираюсь поместить в триггер, затем глобально заменяю # ничем и добавляю код создания триггера. )
Возможно, вы можете использовать SQL CLR для трассировки, как описано здесь.Как войти в T-SQL
Можете ли вы взять свою функцию и сделать ее вторую копию, но возвращая тип таблицы с дополнительным столбцом для вашей отладочной информации.
Например, функция mySum ниже
CREATE FUNCTION mySum
(
@param1 int,
@param2 int
)
RETURNS INT AS
BEGIN
DECLARE @mySum int
SET @mySum = @param1
SET @mySum = @mySum + @param2
RETURN @mySum
END
GO
SELECT dbo.mySum(1, 2)
Превратился бы в
CREATE FUNCTION mySumDebug
(
@param1 int,
@param2 int
)
RETURNS @myTable TABLE
(
[mySum] int,
[debug] nvarchar(max)
)
AS
BEGIN
DECLARE @debug nvarchar(max)
SET @debug = 'Declare @mySum variable. '
DECLARE @mySum int
SET @debug = @debug + 'Set @mySum = @param1(' + CONVERT(nvarchar(50), @param1) + ') '
SET @mySum = @param1
SET @debug = @debug + 'Add @param2(' + CONVERT(nvarchar(50), @param2) + ') to @mySum(' + CONVERT(nvarchar(50), @mySum) + ') '
SET @mySum = @mySum + @param2
SET @debug = @debug + 'Return @mySum variable. '
INSERT @myTable (mySum, debug) VALUES (@mySum, @debug)
RETURN
END
GO
SELECT mySum, debug FROM dbo.mySumDebug(1, 2)
Не идеальное решение, но полезно просто вернуть текст, который поможет отследить ошибку.
Я использую SQL SPY, который делает то, что вы ищете, и даже больше.
Документация по функциям SQL SPY
Анализатор входящего SQL-запроса SQL SPY показывает входящий код SQL каждого соединения (включая отслеживание операторов DDL и DML).
Эта функция предназначена для MS SQL Server 2005\2008, но будет работать с MS SQL Server 2000 в ограниченном объеме.Он имеет возможность записывать и сообщать о входящем SQL.Как использовать функции:Видеть
Раскрытие информации:Я являюсь частью команды SQL SPY.