web-dev-qa-db-ja.com

イベントを実行したストアドプロシージャを見つける方法

特定のストアドプロシージャが時々消えてしまう問題があり、どのスクリプトがそれを削除するのかを調べる必要があります。このストアドプロシージャの削除に関連するイベントを提供するこのコードを見つけました。

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;

このストアドプロシージャを削除したストアドプロシージャまたはイベントを見つける方法はありますか?アドバイスをお願いします。

6
mayooran

このストアドプロシージャを削除するクエリのDDLトリガーからSQLを取得しても、非常に役立ちます。クエリがストアドプロシージャのダイナミックSQLから、またはリリーススクリプトから、または統合テスト、アプリケーションコードなどからのものである場合、_DROP PROCEDURE ..._のみをキャプチャする可能性があり、手掛かりはあまりありませんそれがどこから実行されているかについて。

ただし、これはDDLトリガーがこれを理解する方法ではないという意味ではありません。単にSQLをキャプチャしてソースを推測するのではなく、このアクションは望ましくないため(そして、削除されるストアドプロシージャを呼び出すコードが壊れる可能性があるため)、単に許可しないでください。 DDLトリガーを使用して_DROP PROCEDURE_イベントをキャプチャし、EVENTDATA()関数から返されたXMLを介して削除されているプロシージャを確認できます。削除されるストアドプロシージャが問題のストアドプロシージャである場合は、次の行に沿って何かを実行します。

_RAISERROR('Ah ha! Caught you red-handed (whatever that means). No DROP for you!', 16, 1);
ROLLBACK;
_

これを行う:

  • ストアドプロシージャが削除されないようにします(とにかく望ましい結果です)。
  • 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
_

このストアドプロシージャを削除しようとすると、次のエラーが発生します。

メッセージ50000、レベル16、状態1、手順PreventDropProcedure、行7
ああハァッ!あなたが(それが何を意味するにせよ)手ぶらで捕まった。あなたのためのドロップはありません!メッセージ3609、レベル16、状態2、行1
トランザクションはトリガーで終了しました。バッチは中止されました。

_DBCC INPUTBUFFER_の代わりに_sys.dm_exec_sql_text_を使用した理由は、_sys.dm_exec_sql_text_が実行中のcurrentクエリを返すためです。 _sys.dm_exec_sql_text_がトリガー自体の中でネイティブにクエリされる場合、最終的に_CREATE TRIGGER..._ステートメントになります。そのDMVがダイナミックSQLまたはサブストアドプロシージャコールでクエリされる場合、それらの特定のクエリが取得され、呼び出し元の_CREATE TRIGGER_も取得されません。それはすべて役に立たない。

対照的に、_DBCC INPUTBUFFER_は、チェーン内の最初のバッチ(現在のクエリだけではない)を報告します。少なくとも、これを使用して、DROP呼び出しにつながる後続の呼び出しをいくつでも追跡できます。


また、これがときどきしか発生しないことを考えると、誰かが_CREATE PROCEDURE_が呼び出される直前に_DROP PROCEDURE_を呼び出していたリリーススクリプトでGOを忘れて、誤って作成されたストアドプロシージャの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
_

次のクエリを実行することで、削除されるストアドプロシージャを含むデータベースでこれが発生するかどうかを検索できます。

_SELECT OBJECT_NAME([object_id]) AS [ObjectName], *
FROM   sys.sql_modules
WHERE  [definition] LIKE N'%DROP%';
_

** 赤利きの語源 (@MartinSmithに感謝)

6
Solomon Rutzky