如何找到执行事件的存储过程?

AnO*_*oul 6 sql-server-2008 stored-procedures profiler extended-events trace

我有一个问题,某个存储过程偶尔会消失,我需要找出哪个脚本删除了它。我发现这段代码给出了与删除这个存储过程相关的事件。

DECLARE @path NVARCHAR(260);

SELECT 
@path = REVERSE(SUBSTRING(REVERSE([path]), 
CHARINDEX(CHAR(92), REVERSE([path])), 260)) + N'log.trc'
FROM    sys.traces
WHERE   is_default = 1;

SELECT 
  LoginName,
  HostName,
  StartTime,
  ObjectName,
  TextData
FROM sys.fn_trace_gettable(@path, DEFAULT)
WHERE EventClass = 47    -- Object:Deleted
AND EventSubClass = 1
AND ObjectName like N'%usp_GetPendingConfiguration%'
ORDER BY StartTime DESC;
Run Code Online (Sandbox Code Playgroud)

有没有办法可以找到哪个存储过程或事件删除了这个存储过程?请指教。

Sol*_*zky 6

对于删除此存储过程的任何查询,从 DDL 触发器获取 SQL 只会有很大帮助。如果查询来自存储过程中的动态 SQL,或来自发布脚本,或集成测试、应用程序代码等,那么您可能只会捕获DROP PROCEDURE ...那些并没有提供太多线索的被处决。

然而,这并不意味着 DDL 触发器不是解决这个问题的方法。与其仅仅捕获 SQL 并尝试推断源,因为此操作是不需要的(并且可能会破坏调用正在删除的存储过程的任何代码),因此应该简单地禁止它。您可以DROP PROCEDURE使用 DDL 触发器捕获事件,然后通过从EVENTDATA()函数返回的 XML 检查正在删除哪个过程。如果正在删除的存储过程是有问题的存储过程,则执行以下内容:

RAISERROR('Ah ha! Caught you red-handed (whatever that means). No DROP for you!', 16, 1);
ROLLBACK;
Run Code Online (Sandbox Code Playgroud)

这样做:

  • 将防止存储过程被删除(无论如何这是期望的结果)。
  • 将识别 DROP 查询的来源。可能有多个来源,特别是如果过程调用是嵌套的。此方法会将错误冒泡,以便发起呼叫的任何人或任何人都将看到错误消息并可能提醒您,因为他们的操作未成功完成。
  • 不会阻止任何其他物体被丢弃。

以下是一个更完整的示例,包括记录事件的功能,以防万一,因为它至少可以让您了解哪些人或进程正在执行此操作,以及执行此操作的频率:

CREATE TRIGGER [PreventDropGetPendingConfiguration]
ON DATABASE
FOR DROP_PROCEDURE
AS
  SET NOCOUNT ON;

  IF (EVENTDATA().value(N'(EVENT_INSTANCE/ObjectName/text())[1]', 'sysname')
        = N'usp_GetPendingConfiguration')
  BEGIN

    -- store values in variables as ROLLBACK will erase EVENTDATA()
    DECLARE @EventTime DATETIME,
            @LoginName sysname, -- lower-case for case-sensitive servers
            @UserName sysname, -- lower-case for case-sensitive servers
            @CommandText NVARCHAR(MAX),
            @SPID INT;

    DECLARE @InputBuffer TABLE
    (
      EventType NVARCHAR(30),
      [Parameters] SMALLINT,
      EventInfo NVARCHAR(4000)
    );

    SELECT @EventTime =
                 EVENTDATA().value(N'(EVENT_INSTANCE/PostTime/text())[1]', 'DATETIME'),
           @LoginName =
                 EVENTDATA().value(N'(EVENT_INSTANCE/LoginName/text())[1]', 'sysname'),
           @UserName =
                 EVENTDATA().value(N'(EVENT_INSTANCE/UserName/text())[1]', 'sysname'),
           @CommandText =
                EVENTDATA().value(N'(EVENT_INSTANCE/TSQLCommand/CommandText/text())[1]',
                'NVARCHAR(MAX)'),
           @SPID = EVENTDATA().value(N'(EVENT_INSTANCE/SPID/text())[1]', 'INT');

    -- RollBack now else logging will also get Rolled Back ;-)
    ROLLBACK;

    IF (OBJECT_ID(N'dbo.LoggyLog') IS NULL)
    BEGIN
      CREATE TABLE dbo.LoggyLog
      (
        LoggyLogID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
        EventTime DATETIME NOT NULL,
        LoginName sysname, -- lower-case for case-sensitive servers
        UserName sysname, -- lower-case for case-sensitive servers
        CommandText NVARCHAR(MAX) NOT NULL,
        SPID INT NOT NULL,
        EventInfo NVARCHAR(4000) NULL
    );
    END;

    DECLARE @SQL NVARCHAR(MAX);
    SET @SQL = N'DBCC INPUTBUFFER ( ' + CONVERT(NVARCHAR(10), @SPID)
               + N' ) WITH NO_INFOMSGS;';

    INSERT INTO @InputBuffer (EventType, [Parameters], EventInfo)
      EXEC(@SQL);

    INSERT INTO dbo.LoggyLog (EventTime, LoginName, UserName, CommandText,
                              SPID, EventInfo)
      SELECT  @EventTime, @LoginName, @UserName, @CommandText, @SPID, tmp.EventInfo
      FROM    @InputBuffer tmp;

    RAISERROR('Ah ha! Caught you red-handed (whatever that means **). No DROP for you!',
              16, 1);
  END;

GO
Run Code Online (Sandbox Code Playgroud)

任何删除此存储过程的尝试都将收到以下错误:

Msg 50000, Level 16, State 1, Procedure PreventDropProcedure, Line 7
啊哈!当场抓住你(不管那意味着什么)。没有 DROP 给你!消息 3609,级别 16,状态 2,第 1 行
事务在触发器中结束。该批次已中止。

我使用DBCC INPUTBUFFER而不是的原因sys.dm_exec_sql_textsys.dm_exec_sql_text返回正在执行的当前查询。如果sys.dm_exec_sql_text在触发器本身内进行本地查询,您将得到该CREATE TRIGGER...语句。如果在动态 SQL 或子存储过程调用中查询该 DMV,那么您将获得这些特定查询,甚至不会获得调用它们的查询CREATE TRIGGER。所有这些都是没有用的。

相比之下,DBCC INPUTBUFFER报告链中的第一批(不仅仅是当前查询),并且至少可以用于跟踪导致调用的任意数量的后续调用DROP


此外,鉴于这只是有时发生,有人可能忘记了GO在调用CREATE PROCEDURE之前正在执行的发布脚本中的 a DROP PROCEDURE,并且不小心将DROP查询作为正在创建的存储过程的一部分(这种情况更常发生在GRANT EXECUTE语句,因为它们通常遵循CREATE PROCEDURE语句)。这可能是由于发布脚本中的以下内容而发生的:

CREATE PROCEDURE dbo.ProcName
AS
...

-- missing GO !!!!

IF (OBJECT_ID(N'dbo.ProcGettingDropped') IS NOT NULL)
BEGIN
  DROP PROCEDURE dbo.ProcGettingDropped;
END;
GO -- this GO terminates the CREATE PROCEDURE statement
Run Code Online (Sandbox Code Playgroud)

您可以通过运行以下查询在包含正在删除的存储过程的数据库中搜索此事件的出现:

SELECT OBJECT_NAME([object_id]) AS [ObjectName], *
FROM   sys.sql_modules
WHERE  [definition] LIKE N'%DROP%';
Run Code Online (Sandbox Code Playgroud)

**红手的词源(感谢@MartinSmith)