【问题标题】:Trouble with Audit-Logout Event Handler审核注销事件处理程序的问题
【发布时间】:2021-07-21 03:17:36
【问题描述】:

我需要能够“捕捉”SQL Server 会话的结束,以便我可以在会话使用的任何数据库上运行例程。我找到了一些队列通知示例,这就是我目前正在尝试使用的。

在下面的代码中,它按原样工作。但是,当我在“激活”存储过程中删除游标 Open 上的 cmets 时,@@ROWCOUNT 返回 0,然后队列被停用。进一步的消息只是继续建立。执行RECEIVE TOP(1) * FROM dbo.queNotification 会出错:

Msg 9617, Level 16, State 1, Procedure dbo.ReceiveEvent, Line 12 [Batch Start Line 177]
The service queue "queNotification" is currently disabled.

再次启用队列后,我尝试手动运行例程...它运行良好。它只是不想在代理设置下运行。

create table ##ENLog (
       [msg] varchar(max),
       [when] datetime2 NOT NULL CONSTRAINT DF_LogoutAudit__time_acquired DEFAULT sysdatetime()
       )

CREATE DATABASE ENTesting;
GO
ALTER DATABASE ENTesting   SET ENABLE_BROKER;
GO
USE ENTesting;

CREATE QUEUE queNotification;
CREATE SERVICE svcNotification ON QUEUE queNotification ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
CREATE EVENT NOTIFICATION enEvents ON SERVER FOR AUDIT_LOGOUT TO SERVICE 'svcNotification', 'current database';
GO

ALTER QUEUE dbo.queNotification WITH ACTIVATION (PROCEDURE_NAME = dbo.ReceiveEvent, STATUS = OFF);
ALTER QUEUE dbo.queNotification WITH STATUS = OFF;
GO

CREATE OR ALTER PROCEDURE dbo.ReceiveEvent AS
    SET NOCOUNT ON
    DECLARE @MsgBody XML

    WHILE (1 = 1)
    BEGIN
        BEGIN TRANSACTION
        insert into ##ENLog (msg) values ('START');

        WAITFOR (                                       -- Receive the next available message from the queue
            RECEIVE TOP(1)                              -- just handle one message at a time
            @MsgBody = CAST(message_body AS XML)
            FROM queNotification), TIMEOUT 1000         -- if the queue is empty for one second, relax
        IF (@@ROWCOUNT = 0)                             -- If we didn't get anything, bail out
        BEGIN
            ROLLBACK TRANSACTION;                       -- this will undo the RECEIVE
            insert into ##ENLog (msg) values ('NO MORE ROWS');
            BREAK;
        END ELSE BEGIN
            BEGIN TRAN
            insert into ##ENLog (msg) values ('GOT ROW');
            declare @spid       smallint     = @MsgBody.value('(/EVENT_INSTANCE/SPID)[1]',           'smallint')
            declare @dbid       int          = @MsgBody.value('(/EVENT_INSTANCE/DatabaseID)[1]',     'int')
            declare @host_name  sysname      = @MsgBody.value('(/EVENT_INSTANCE/HostName)[1]',       'sysname')
            declare @login_name sysname      = @MsgBody.value('(/EVENT_INSTANCE/LoginName)[1]',      'sysname')

            declare @msg nvarchar(max) =  'spid ' + convert(nvarchar, @spid) + char(10) + char(9) + 'dbid ' + convert(nvarchar, @dbid) + char(10) + char(9) + 'host ' + @host_name   + char(10) + char(9) + 'login ' + @login_name + char(10) + char(9);
            insert into ##ENLog (msg) values (@msg);

            declare @cur cursor
            declare @dbname sysname
            begin
                insert into ##ENLog (msg) values ('Start Cursor');
                SET @cur = CURSOR FOR
                    select name from sys.databases d
                        where database_id>4 and object_id([name] + '.mds.DeinitSession') IS NOT NULL
                BEGIN TRY
                    print '' -- nop
----    uncomment anything below here and it stops working
----                    OPEN @cur
----                    insert into ##ENLog (msg) values ('Cursor Opened');
----                    FETCH NEXT FROM @cur INTO @dbname
----                    insert into ##ENLog (msg) values ('Fetch Called');
----                    while @@FETCH_STATUS = 0 begin
----                        insert into ##ENLog (msg) values ('Calling Deinit on ' + @dbname);
----
----                        declare @sql nvarchar(256) = 'USE btrtest' + char(10) + 'insert into ##ENLog (msg) values (''deinit ' + @dbname + ''')'
----                        insert into ##ENLog (msg) values (@sql);
----                        execute(@sql)
----                        FETCH NEXT FROM @cur INTO @dbname
----                    end
----                    insert into ##ENLog (msg) values ('Close Cursor');
----                    CLOSE @cur
----                    DEALLOCATE @cur
                END TRY BEGIN CATCH
                    set @msg = N'Exception Error: ' + N' Msg ' + convert(nvarchar, ERROR_NUMBER()) + N', Level ' + convert(nvarchar, ERROR_SEVERITY()) + N', Stat '  + convert(nvarchar, ERROR_STATE()) + N', Line ' + convert(nvarchar, ERROR_LINE()) + N', Proc ' + isnull(ERROR_PROCEDURE(),'--') + N'  ' + ERROR_MESSAGE();
                    insert into ##ENLog (msg) values (@msg);
                END CATCH
            end

            insert into ##ENLog (msg) values ('Commit');
            COMMIT TRANSACTION
       
            -- 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
GO


ALTER QUEUE dbo.queNotification
    WITH ACTIVATION ( PROCEDURE_NAME = dbo.ReceiveEvent, STATUS = ON, MAX_QUEUE_READERS = 1, EXECUTE AS OWNER)
GO
ALTER QUEUE dbo.queNotification WITH STATUS = ON;
GO



SELECT * FROM dbo.queNotification;

select * from ##ENLog

【问题讨论】:

    标签: sql-server event-handling message-queue database-cursor


    【解决方案1】:

    Service Broker 有一个有害消息检测/缓解机制,如果连续发生五次回滚 (iirc),它会关闭整个系统。对我来说很奇怪的一件事是:

    IF (@@ROWCOUNT = 0)                             -- If we didn't get anything, bail out
            BEGIN
                ROLLBACK TRANSACTION;                       -- this will undo the RECEIVE
                insert into ##ENLog (msg) values ('NO MORE ROWS');
                BREAK;
            END
    

    如果你没有得到任何行结束过程,不要回滚。

    【讨论】:

    • 很高兴知道这一点。谢谢本。 RollBack 来自我从中获取的示例代码。我会删除它。
    猜你喜欢
    • 2010-09-22
    • 2011-05-16
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    相关资源
    最近更新 更多