Можно ли получить стек вызовов выполнения в триггере?

16

У меня есть 10 хранимых процедур, и каждая из них делает вставки в одну таблицуX.

Возможно ли в теле триггера tableX получить, какой объект вызывает модификацию tableX (хранится в proc1 или sp2 или ....)?

Спасибо.

Гарик
источник

Ответы:

9

Да, можно определить работающий код, используя системную функцию @@ procid , и лучше присвоить OBJECT_NAME (@@ PROCID) полное имя.

Определение: «Возвращает идентификатор объекта (ID) текущего модуля Transact-SQL. Модуль Transact-SQL может быть хранимой процедурой, пользовательской функцией или триггером. @@ PROCID нельзя указывать в модулях CLR или в обработчик доступа к данным. "

Вы можете прочитать об этом здесь .

Другой вариант - проверить план sql текущего spid и сохранить эту информацию в таблице журналов. Пример запроса, который будет использоваться в каждой процедуре для сохранения данных аудита:

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

Может быть, там слишком много деталей .. но я верю, что вы поняли идею.

Третий вариант - использовать информацию context_info для текущего сеанса SP. И связать где-нибудь контекстную информацию, сохраненную там с каждой процедурой. Например, в процедуре1 вы пишете 111 в контекст, в процедуре2 вы пишете 222 .. и так далее.

Много больше информации о context_info вы можете прочитать в этом вопросе SO .

Мэриан
источник
1
1) OBJECT_NAME (@@ PROCID) в триггере возвращает имя триггера :(. 2) необходимо иметь информацию только в триггере. 3) context_info - это решение. Благодарю.
Гарик
1
Да, внутри триггера OBJECT_NAME(@@PROCID)возвращается имя триггера, а не вызывающий процесс.
ProfK
Это просто неправильно. Он возвращает имя триггера, а не процедуры вызова, как запросил OP
Reversed Engineer
Согласитесь, ответ неверный. CONTEXT_INFO работает, если вы можете изменить вышестоящую процедуру.
Том Уорфилд
3

Я тоже хотел это сделать. Спасибо за ответ. Поскольку я все еще здесь, я опубликую свой тест, чтобы сэкономить время других :)

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
Джим Браун
источник
2

XEvents предоставляют другой способ получения стека T-SQL, хотя SQL Server 2008 может не поддерживать используемый тип событий. Решение состоит из триггера, ошибки и сеанса XEvent. Я взял пример Джима Брауна, чтобы показать, как он работает.

Прежде всего, я протестировал решение для SQL Server 2016 SP2CU2 Dev Edition. SQL Server 2008 поддерживает некоторые EXevent, но у меня нет ни одного экземпляра, чтобы я не смог его протестировать.

Идея состоит в том, чтобы сгенерировать пользовательскую ошибку в фиктивном блоке try-catch, а затем перехватить ошибку в сеансе XEvent с tsql_stackдействием. SQLSERVER.error_reportedТип XEvent может перехватывать все ошибки, даже если блок try-catch их перехватывает. В конце sys.dm_exec_sql_textизвлеките запросы T-SQL из дескрипторов запросов, которые tsql_stackдает действие.

Пример из ответа Джима Брауна, который я разработал, показан ниже. Триггер вызывает ошибку с текстом «поймай меня». Сессия XEvent ловит ошибки только с текстом типа «поймай меня».

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

Теперь, если вы запустите сеанс XEvent (SSMS, Обозреватель объектов, Управление, Расширенные события, Сеансы, catch_insertion_into_Test), выполните usp_RootProcIDTest и посмотрите кольцевой буфер сеанса XEvent, вы должны увидеть XML, который состоит из узла <action name="tsql_stack" package="sqlserver">. Существует последовательность узлов кадра. Поместите значения handleатрибута в системную функцию 'sys.dm_exec_sql_text' и вуаля:

-- 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);

Пример стека вызовов выполнения

XEvent позволит вам сделать гораздо больше, чем это! Не упустите возможности узнать их!

Василий Кисель
источник