É possível obter a pilha de chamadas de execução em um gatilho?

16

Eu tenho 10 procedimentos armazenados e cada um deles faz INSERTs em uma tabelaX.

É possível, em um corpo de gatilho da tabelaX, obter qual objeto causa a modificação da tabelaX (proc1 armazenado ou sp2 ou ....)?

Obrigado.

garik
fonte

Respostas:

9

Sim, é possível identificar o código em execução usando a função do sistema @@ procid e melhor OBJECT_NAME (@@ PROCID) para ter o nome completo.

Definição: "Retorna o identificador do objeto (ID) do módulo Transact-SQL atual. Um módulo Transact-SQL pode ser um procedimento armazenado, função definida pelo usuário ou gatilho. @@ PROCID não pode ser especificado nos módulos CLR ou no módulo fornecedor de acesso a dados do processo ".

Você pode ler sobre isso aqui .

Outra opção seria verificar o plano sql do spid atual e salvar essas informações em uma tabela de log. Uma consulta de amostra a ser usada em cada procedimento para salvar dados de auditoria seria:

select sp.hostname, sp.program_name, sp.loginame,
    st.text as query_text
from sysprocesses sp
cross apply sys.dm_exec_sql_text(sp.sql_handle) as st  
where sp.spid = @@spid

Talvez haja muitos detalhes lá ... mas acredito que você entendeu a idéia.

Uma terceira opção seria usar as informações context_info na sessão do SP atual. E associe em algum lugar a informação de contexto salva lá com cada procedimento. Por exemplo, no procedimento1, você escreve 111 no contexto, no procedimento2, escreve 222 .. e assim por diante.

Muito mais informações sobre context_info você pode ler nesta pergunta do SO .

Marian
fonte
1
1) OBJECT_NAME (@@ PROCID) no gatilho retorna o nome do gatilho :(. 2) é necessário ter informações apenas no gatilho. 3) context_info é uma solução. obrigado.
Garik
1
Sim, dentro de um gatilho OBJECT_NAME(@@PROCID)retorna o nome do gatilho, não o processo de chamada.
ProfK
Isto é simplesmente errado. Ele retorna o nome do gatilho, não do procedimento de chamada, conforme solicitado pelo OP #
Reversed Engineer
Concordo, a resposta está errada. CONTEXT_INFO funciona se você puder modificar o procedimento upstream.
Tom Warfield
3

Eu também queria fazer isso. Obrigado pela resposta. Como ainda estou aqui, publicarei meu teste para economizar tempo :)

CREATE TABLE  Test ( TestID INT )
GO

CREATE TRIGGER TestTrigger ON Test
FOR INSERT
AS

SELECT CAST(CONTEXT_INFO() AS NVARCHAR(128));
GO

CREATE PROCEDURE usp_ProcIDTest
AS

DECLARE @ProcedureName VARBINARY(MAX) = CAST(OBJECT_NAME(@@PROCID) AS VARBINARY(MAX))
SET CONTEXT_INFO @ProcedureName

INSERT INTO Test ( TestID ) VALUES ( 1 ) 

GO

EXEC usp_ProcIDTest
GO

DROP TABLE Test
GO
Jim Brown
fonte
2

O XEvents fornece outra maneira de tornar conhecida uma pilha T-SQL, embora o SQL Server 2008 possa não suportar um tipo de evento usado. A solução consiste em um gatilho, um erro e uma sessão XEvent. Peguei o exemplo de Jim Brown para mostrar como funciona.

Antes de tudo, testei a solução para o SQL Server 2016 SP2CU2 Dev Edition. O SQL Server 2008 suporta alguns EXevent, mas não tenho nenhuma instância para não poder testá-lo.

A idéia é gerar um erro do usuário em um bloco de tentativa e captura simulado e capturar o erro dentro de uma sessão do XEvent com tsql_stackação. SQLSERVER.error_reportedO tipo XEvent pode capturar todos os erros, embora um bloco try-catch os prenda. No final, sys.dm_exec_sql_textextraia as consultas T-SQL das alças de consulta que tsql_stackação fornece.

Um exemplo da resposta de Jim Brown, que desenvolvi, é mostrado abaixo. Um gatilho gera o erro com o texto 'catch me'. A sessão do XEvent captura erros apenas com o texto como 'catch me'.

CREATE TABLE  Test ( TestID INT )

GO

CREATE TRIGGER TestTrigger ON Test
FOR INSERT
AS
BEGIN TRY
    SET XACT_ABORT OFF; -- REALLY IMPORTANT!
    /* make an catching a great deal more interesting */
    DECLARE @TestID NVARCHAR(MAX) ;
    SELECT TOP (1) @TestID = CAST(ins.TestID AS NVARCHAR(MAX)) FROM inserted AS ins ;
    RAISERROR (N'catch_me TestID = "%s"' , 11 , 0 , @TestID) ;
END TRY BEGIN CATCH /* NOTHING TO DO */ END CATCH

GO

CREATE PROCEDURE usp_ProcIDTest
AS
INSERT INTO Test ( TestID ) VALUES ( 1 ) 

GO

CREATE PROCEDURE usp_RootProcIDTest
AS
EXEC usp_ProcIDTest

GO

-- This XEvent session definition was kindly provided by XEvent 'New Session' wizard.
CREATE EVENT SESSION [catch_insertion_into_Test] ON SERVER 
ADD EVENT sqlserver.error_reported(
    ACTION(package0.callstack,sqlserver.client_app_name,sqlserver.client_hostname,sqlserver.client_pid,sqlserver.database_id,sqlserver.query_hash,sqlserver.query_plan_hash,sqlserver.server_principal_name,sqlserver.session_id,sqlserver.session_nt_username,sqlserver.sql_text,sqlserver.tsql_frame,sqlserver.tsql_stack,sqlserver.username,sqlserver.context_info,sqlserver.plan_handle)
    WHERE ([message] like N'catch_me%'))
ADD TARGET package0.ring_buffer(SET max_memory=(10240))
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=ON)

GO

Agora, se você iniciar a sessão do XEvent (SSMS, Pesquisador de Objetos, Gerenciamento, Eventos Estendidos, Sessões, catch_insertion_into_Test), execute usp_RootProcIDTest e procure o buffer de anel da sessão do XEvent, deverá ver o XML que consiste no nó <action name="tsql_stack" package="sqlserver">. Há uma sequência de nós de quadros. Coloque os valores do handleatributo a na função do sistema 'sys.dm_exec_sql_text' e voilà:

-- REPLACE MY HANDLES WITH YOURS
SELECT * FROM sys.dm_exec_sql_text(0x03000800D153096910272C01A6AA000000000000000000000000000000000000000000000000000000000000);
SELECT * FROM sys.dm_exec_sql_text(0x030008000A78FD6912272C01A6AA000001000000000000000000000000000000000000000000000000000000);
SELECT * FROM sys.dm_exec_sql_text(0x03000800439CF16A13272C01A6AA000001000000000000000000000000000000000000000000000000000000);

Um exemplo de pilha de chamadas de execução

O XEvent permite que você faça muito mais do que isso! Não perca oportunidades de aprendê-las!

Basil Kisel
fonte