Cómo rastrear llamadas de función T-SQL
-
20-08-2019 - |
Pregunta
Estoy tratando de depurar un evaluador de fórmulas bastante complicado escrito en UDF T-SQL (no pregunte) que recursivamente (pero indirectamente a través de una función intermedia) se llama a sí mismo, bla, bla.
Y, por supuesto, tenemos un error.
Ahora, usando las declaraciones PRINT (que luego se pueden leer desde ADO.NET mediante la implementación de un controlador para el evento InfoMessage), puedo simular un seguimiento de los procedimientos almacenados.
Hacer lo mismo para UDF da como resultado un mensaje de tiempo de compilación:
Invalid use of side-effecting or time-dependent operator in 'PRINT' within a function.
Recibo el mensaje (PRINT hace algunas cosas como restablecer @@ROWCOUNT
que definitivamente es un no-no en UDF, pero ¿cómo puedo rastrear las llamadas? Quiero que se imprima este rastreo, así puedo estudiarlo sin distraerse pasando por las llamadas en el depurador ...
EDITAR: he intentado utilizar el Analizador de SQL (esta fue la primera vez para mí), pero no puedo averiguar qué rastrear: aunque puedo obtener el rastreo para genera las consultas enviadas a la base de datos, son opacas en el sentido de que no puedo profundizar en los Expression-UDF llamados: puedo rastrear el Procedimiento almacenado real invocado, pero los UDF llamados por este procedimiento no están listados. ¿Me estoy perdiendo de algo? Supongo que no ...
EDIT # 2: Aunque la respuesta (auto) aceptada rastrea las llamadas de función, muy útil, gracias, no ayuda a descubrir qué parámetros se pasaron a la función. Esto, por supuesto, es esencial en depuración funciones recursivas. Publicaré si encuentro alguna solución ...
Solución
¿Por qué no usar SQL Profiler con eventos de nivel de declaración agregados?
Editar : agregue eventos para procedimientos almacenados: SP: inicio de SP o SP: finalizado Use variables para depurar si es necesario, es decir, establezca @ debug = 'estoy aquí'; Los UDF, aunque no son procedimientos almacenados técnicamente, se rastrearán con los eventos de nivel de declaración.
Otros consejos
En el generador de perfiles SQL, necesita: SP: Inicio, SP: StmtStarting, SP: Completado, SQL: BatchStarting. Luego, obtiene cada entrada, sale de las funciones / procedimientos almacenados.
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
con esto, obtengo:
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
¿es suficiente para ti?
Esto se parece a lo que necesita pero solo está disponible en las versiones de equipo / profesional de Visual Studio.
Use el Analizador de SQL, le recomiendo que se exceda al agregar eventos la primera vez, lo que le permitirá tener una idea de lo que necesita. Sin probar, agregaría los eventos para SP: StmtStarted (o Completed o ambos), SQL: StmtStarted (nuevamente Completed o Both).
Secundo la sugerencia de SQL Profiler. Tómese un tiempo para configurarlo de modo que solo los eventos que le interesan se registren para reducir el tamaño de salida. Puede enviar la traza a un archivo; con frecuencia, he cargado ese archivo nuevamente en una tabla para permitir el análisis. (extremadamente útil para el análisis de rendimiento, aunque sin duda alguien me dirá que 2008 tiene todo esto incorporado en algún lugar ...)
A veces no tendrá permisos para ejecutar SQL Profiler, ya que ralentiza el servidor; solicite a su DBA que le otorgue permiso en su servidor Dev. No deberían tener ningún problema con eso.
Bueno, en el pasado tuve que tomar valores típicos que estarían en la UDF y luego ejecutar solo la parte udf en una ventana de consulta separada como SQL directo, no un udf usando los valores típicos como variables establecidas con una declaración y un Establecer declaración. Si se ejecuta desde una tabla en lugar de tener solo un valor, configuraría una tabla temporal o variable de tabla con los valores de entrada y luego los ejecutaría a través del sql en el UDF (pero nuevamente como SQL directo no un UDF) a través de un cursor. Al ejecutar SQL directo, podría tener declaraciones impresas para ver qué está sucediendo. Sé que esto es un dolor, pero funciona. (Realizo un proceso similar al crear / depurar activadores, configuro # insertado y # eliminado con mis valores de prueba y luego pruebo el código que pretendo poner en el activador, luego global reemplaza el # por nada y agrego el código de activación de creación. )
Tal vez pueda usar SQL CLR para hacer el seguimiento como se describe aquí Cómo iniciar sesión en T-SQL
¿Puede tomar su función y hacer una segunda copia, pero devolviendo un tipo de tabla con una columna adicional para su información de depuración?
Por ejemplo, la función mySum a continuación
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)
Se convertiría en
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)
No es una solución ideal, pero es útil solo para devolver texto para ayudar a localizar un error.
Uso SQL SPY que hace lo que estás buscando y más.
Documentación de funciones SQL SPY
El sniffer SQL entrante de SQL SPY muestra el código SQL entrante de cada conexión (incluye el seguimiento de sentencias DDL y DML)
Esta característica está diseñada para MS SQL Server 2005 \ 2008, pero funcionará con MS SQL Server 2000 en un alcance limitado. Tiene la capacidad de grabar e informar sobre SQL entrante. Cómo usar las funciones: ver
Divulgación: soy parte del equipo SQL SPY.