【问题标题】:SQL Server logging failed queriesSQL Server 记录失败的查询
【发布时间】:2017-04-12 08:59:21
【问题描述】:

我正在尝试实现系统范围的日志记录,它将在我们的数据库中记录所有失败的存储过程执行,并且我正在查看扩展事件。

我做了一些研究,使用以下代码似乎很容易捕获失败的语句:

--Create an extended event session
CREATE EVENT SESSION what_queries_are_failing ON SERVER
ADD EVENT sqlserver.error_reported (
    ACTION (sqlserver.sql_text
        , sqlserver.tsql_stack
        , sqlserver.database_id
        , sqlserver.username
        )
    WHERE ([severity] > 10)
    )
ADD TARGET package0.asynchronous_file_target (
    SET filename = 'C:\XEventSessions\what_queries_are_failing.xel'
    , metadatafile = 'C:\XEventSessions\what_queries_are_failing.xem'
    , max_file_size = 5
    , max_rollover_files = 5
    )
    WITH (MAX_DISPATCH_LATENCY = 5 SECONDS)
GO

-- Start the session
ALTER EVENT SESSION what_queries_are_failing ON SERVER STATE = START
GO

;WITH events_cte
AS (
    SELECT DATEADD(mi, DATEDIFF(mi, GETUTCDATE(), CURRENT_TIMESTAMP), xevents.event_data.value('(event/@timestamp)[1]', 'datetime2')) AS [err_timestamp]
        , xevents.event_data.value('(event/data[@name="severity"]/value)[1]', 'bigint') AS [err_severity]
        , xevents.event_data.value('(event/data[@name="error_number"]/value)[1]', 'bigint') AS [err_number]
        , xevents.event_data.value('(event/data[@name="message"]/value)[1]', 'nvarchar(512)') AS [err_message]
        , xevents.event_data.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS [sql_text]
        , xevents.event_data
    FROM sys.fn_xe_file_target_read_file('S:\XEventSessions\what_queries_are_failing*.xel', 'S:\XEventSessions\what_queries_are_failing*.xem', NULL, NULL)
    CROSS APPLY (
        SELECT CAST(event_data AS XML) AS event_data
        ) AS xevents
    )
SELECT *
FROM events_cte
ORDER BY err_timestamp;

但是我想立即将失败的语句存储到一个表中,我们称之为Logs.Errors,但我找不到方法,并且上面的方法必须作为预定的工作。

现在,我们的程序如下所示:

CREATE PROCEDURE [dbo].[MyProcedure]
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        SELECT 1;
    END TRY
    BEGIN CATCH
        EXECUTE Logs.PrintError;
        EXECUTE Logs.LogError;
    END CATCH
END

Logs.LogError 过程使用 DBCC INPUTBUFFER(); 但它不捕获参数,只是捕获执行的确切过程。这就是我能从中得到的一切:

+----------------------------+-----------+-----------+------------------------------+
|        ErrorMessage        | EventType | Parameter |          Statement           |
+----------------------------+-----------+-----------+------------------------------+
| Incorrect syntax near '.'. | RPC Event |         0 | DbName.dbo.FailedProcedure;1 |
+----------------------------+-----------+-----------+------------------------------+

如果可能的话,我正在寻找一种方法,通过强制 DBCC INPUTBUFFER() 捕获整个语句或 XE 将记录直接插入某个表来使 DBCC INPUTBUFFER() 工作。

任何问题 - 让我知道。

【问题讨论】:

  • 能否详细说明he upper method would have to work as a scheduled job.
  • @TheGameiswar 当然。我记得扩展事件可以在后台运行并将失败的查询信息存储到给定的文件中。然后根据时间表(假设每小时一次),我可以读取该文件并将记录插入Logs.Errors 表。现在更有意义了吗?
  • 你不需要将扩展​​事件启动和停止事件作为作业运行,一旦启动,它就会在后台运行
  • 你可以使用批量启动,停止事件,看看我在dba.se上的回答stackoverflow.com/questions/36098446/…

标签: sql-server stored-procedures dbcc extended-events


【解决方案1】:

我发现 XEvents 非常适合在事件发生时监控它们。但是,它们没有提供“处理”观察到的事件的机制。为了填补这个空白,我使用了Event Notifications。我经常将它们描述为异步 DDL 触发器。我会让你决定 tl;dr 的定义是否准确。

如果您想尝试一下事件通知,可以从以下脚本开始(抱歉,它真的很长)。如果您有任何问题/问题,请告诉我。我会尽量回复。

--Create these objects within a database that has service broker enabled.
USE DbaData
GO

--Drop objects first before trying to create them (in specific sequence).
IF EXISTS (
    SELECT *
    FROM sys.services
    WHERE name = 'svcUserErrorReportedNotification'
)
    DROP SERVICE svcUserErrorReportedNotification;
GO

IF EXISTS (
    SELECT *
    FROM sys.service_queues
    WHERE name = 'queUserErrorReportedNotification'
)
    DROP QUEUE queUserErrorReportedNotification;
GO

IF EXISTS (
    SELECT * 
    FROM sys.server_event_notifications 
    WHERE name = 'enUserErrorReportedEvents'
)
    DROP EVENT NOTIFICATION enUserErrorReportedEvents
    ON SERVER
GO

--Create a queue just for user error events.
CREATE QUEUE queUserErrorReportedNotification
GO

--Create a service just for user error events.
CREATE SERVICE svcUserErrorReportedNotification
ON QUEUE queUserErrorReportedNotification ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification])
GO

-- Create the event notification for user error events on the service.
CREATE EVENT NOTIFICATION enUserErrorReportedEvents
ON SERVER
WITH FAN_IN
FOR USER_ERROR_MESSAGE
TO SERVICE 'svcUserErrorReportedNotification', 'current database';
GO

IF EXISTS (
    SELECT *
    FROM INFORMATION_SCHEMA.ROUTINES r
    WHERE r.ROUTINE_SCHEMA = 'dbo' AND r.ROUTINE_NAME = 'ReceiveUserErrorReportedEvent'
)
    DROP PROCEDURE dbo.ReceiveUserErrorReportedEvent 
GO

CREATE PROCEDURE dbo.ReceiveUserErrorReportedEvent
/*****************************************************************************
* Name     : dbo.ReceiveUserErrorReportedEvent
* Purpose  : Runs when there is a USER_ERROR_MESSAGE event.
* Inputs   : None
* Outputs  : None
* Returns  : Nothing
******************************************************************************
* Change History
*   11/28/2016  DMason  Created
******************************************************************************/
AS
BEGIN
    SET NOCOUNT ON
    DECLARE @MsgBody XML

    WHILE (1 = 1)
    BEGIN
        BEGIN TRANSACTION

        -- Receive the next available message FROM the queue
        WAITFOR (
            RECEIVE TOP(1) -- just handle one message at a time
                @MsgBody = CAST(message_body AS XML)
                FROM queUserErrorReportedNotification
        ), TIMEOUT 1000  -- if the queue is empty for one second, give UPDATE and go away
        -- If we didn't get anything, bail out
        IF (@@ROWCOUNT = 0)
        BEGIN
            ROLLBACK TRANSACTION
            BREAK
        END 
        ELSE
        BEGIN
            --Grab some relevant items from the message body XML (it is EVENTDATA(), btw)
            DECLARE @Login SYSNAME;
            DECLARE @ErrMsgText VARCHAR(MAX);
            DECLARE @ApplicationName VARCHAR(MAX);
            DECLARE @Severity INT;
            DECLARE @ErrorNumber INT;
            DECLARE @DBName SYSNAME;

            SET @Login = @MsgBody.value('(/EVENT_INSTANCE/LoginName)[1]', 'VARCHAR(128)' );
            SET @ErrMsgText = @MsgBody.value('(/EVENT_INSTANCE/TextData)[1]', 'VARCHAR(MAX)' );
            SET @ApplicationName = @MsgBody.value('(/EVENT_INSTANCE/ApplicationName)[1]', 'VARCHAR(MAX)' );
            SET @Severity = @MsgBody.value('(/EVENT_INSTANCE/Severity)[1]', 'INT' );
            SET @ErrorNumber = @MsgBody.value('(/EVENT_INSTANCE/Error)[1]', 'INT' );
            SET @DBName = @MsgBody.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'VARCHAR(128)' );

            --Do stuff here.
            --Log to a table, etc.

            /*
                Commit the transaction.  At any point before this, we 
                could roll back -- the received message would be back 
                on the queue AND the response wouldn't be sent.
            */
            COMMIT TRANSACTION
        END
    END
END
GO

ALTER QUEUE dbo.queUserErrorReportedNotification 
WITH 
STATUS = ON, 
ACTIVATION ( 
    PROCEDURE_NAME = dbo.ReceiveUserErrorReportedEvent, 
    STATUS = ON, 
    MAX_QUEUE_READERS = 1, 
    EXECUTE AS OWNER) 
GO

【讨论】:

  • 天啊。这看起来正是我正在寻找的。我会在我的本地机器上尝试,并在可能的时候立即反馈。谢谢!
  • 我刚刚想到的... USER_ERROR_MESSAGE 可能会产生很多“噪音”。它包括所有严重级别的错误(我认为)。例如,您可能会发现大量严重级别低于 10 的“错误”事件,您可能不会关心这些事件。这可能会比 SQL 处理它的速度更快地淹没队列。
  • 是否可以只过滤超过特定严重性的失败程序?
  • 这真的很棒,感觉就像你把我引向了正确的道路。非常感谢!
  • 不客气!如果遇到问题,请在此处添加一些注释。如果有任何遗漏或其他错误,我很乐意更新脚本/答案。
猜你喜欢
  • 2019-02-16
  • 1970-01-01
  • 2011-09-26
  • 1970-01-01
  • 2014-07-14
  • 2011-02-26
  • 2013-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多