【问题标题】:Updating a table (with audit trigger using Service Broker) in a Transaction在事务中更新表(使用 Service Broker 的审计触发器)
【发布时间】:2011-07-10 22:22:26
【问题描述】:

我们使用服务代理实现了审计功能,并在需要审计的表上实现了触发器。我们面临的问题是,当我们尝试从事务中更新可审计表时,它会引发错误 -

当前交易不能 承诺且不能支持 写入日志文件的操作。 回滚事务。

但是,如果我们从可审计表中删除触发器,则一切正常。是不可能在事务中更新表(带触发器)还是我们最后遗漏了什么?

更新交易

BEGIN TRAN
    update  ActivationKey set OrderLineTransactionId = @orderLineTransactionId, LastUpdated = getUtcdate(), [Status] =2  
    where   PurchaseTransactionId = @transactionid 
        -- Rollback the transaction if there were any errors
            IF @@ERROR <> 0 
                ROLLBACK
            ELSE        
                COMMIT TRAN
END TRAN                

触发器

ALTER TRIGGER [dbo].[ActivationKey_AuditTrigger]
     ON  [dbo].[ActivationKey]
    AFTER INSERT, UPDATE, DELETE 
    AS
    BEGIN
        SET NOCOUNT ON;

        DECLARE @auditBody XML
        Declare @newData nvarchar(MAX)
        DECLARE @DMLType CHAR(1)    
        -- after delete statement
        IF NOT EXISTS (SELECT * FROM inserted)
        BEGIN   
            SELECT  @auditBody = (select * FROM deleted AS t FOR XML AUTO, ELEMENTS),
                    @DMLType = 'D'
        END 
        -- after update or insert statement
        ELSE
        BEGIN
                --after Update Statement
            IF EXISTS (SELECT * FROM deleted)
              begin
                    SELECT      @auditBody = (select * FROM deleted AS t FOR XML AUTO, ELEMENTS)
                    SELECT      @newData = (select * FROM Inserted AS t FOR XML AUTO, ELEMENTS)
                    SELECT      @DMLType = 'U'
              end
              ELSE -- after insert statement
              begin
                    SELECT      @auditBody = (select * FROM inserted AS t FOR XML AUTO, ELEMENTS)
                    SELECT      @DMLType = 'I'
              end
        END

        -- get table name dynamicaly but
        DECLARE @tableName sysname 
        SELECT  @tableName = 'ActivationKey'

        SELECT @auditBody = 
            '<AuditMsg>
                <SourceDb>' + DB_NAME() + '</SourceDb>
                <SourceTable>' + @tableName + '</SourceTable>
                <UserId>' + SUSER_SNAME() + '</UserId>
                <DMLType>' + @DMLType + '</DMLType>
                <ChangedData>' + CAST(@auditBody AS NVARCHAR(MAX)) + '</ChangedData>
                <NewData>' + isnull(@newData,'') + '</NewData>
            </AuditMsg>'
        -- Audit data asynchrounously
        EXEC dbo.procAuditSendData @auditBody
    END 

从触发器中调用的存储过程 (procAuditSendData)

ALTER PROCEDURE [dbo].[procAuditSendData]

( @AuditedData XML ) 作为 开始 开始尝试 声明@dlgId UNIQUEIDENTIFIER,@dlgIdExists BIT 选择@dlgIdExists = 1

    SELECT  @dlgId = DialogId
    FROM    vwAuditDialogs AD 
    WHERE   AD.DbId = DB_ID()
    IF  @dlgId IS NULL
    BEGIN 
        SELECT @dlgIdExists = 0
    END

    -- Begin the dialog, either with existing or new Id
    BEGIN DIALOG @dlgId
        FROM SERVICE    [//Audit/DataSender]                                               
        TO SERVICE      '//Audit/DataWriter', 
                'BAAEA6F1-C97E-4884-8651-2829A2049C46'
        ON CONTRACT     [//Audit/Contract]
    WITH ENCRYPTION = OFF;

    -- add our db's dialog to AuditDialogs table if it doesn't exist yet
    IF @dlgIdExists = 0
    BEGIN 
        INSERT INTO vwAuditDialogs(DbId, DialogId)
        SELECT  DB_ID(), @dlgId
    END
    --SELECT @AuditedData

    -- Send our data to be audited
    ;SEND ON CONVERSATION @dlgId    
    MESSAGE TYPE [//Audit/Message] (@AuditedData)
END TRY
BEGIN CATCH
    INSERT INTO AuditErrors (
            ErrorProcedure, ErrorLine, ErrorNumber, ErrorMessage, 
            ErrorSeverity, ErrorState, AuditedData)
    SELECT  ERROR_PROCEDURE(), ERROR_LINE(), ERROR_NUMBER(), ERROR_MESSAGE(), 
            ERROR_SEVERITY(), ERROR_STATE(), @AuditedData
END CATCH

结束

【问题讨论】:

  • 由于对数据库的每一项操作实际上都发生在一个事务中(无论是由您的代码显式启动,还是在语句开头自动启动,并且如果语句完成且没有错误则提交),因此存在触发器无法在事务上下文之外触发。我想我们需要看一些代码。
  • 我猜这是dbo.ProcAuditSendData内部发生的事情,因为这个问题stackoverflow.com/questions/1039391/…表明这个错误只能发生在CATCH块内。
  • 我简要了解我们需要以某种方式检查 XACT_STATE 但参考周围可用的示例,无法弄清楚我们在 proc 中究竟在哪里检查。

标签: sql sql-server-2008 transactions triggers audit-tables


【解决方案1】:

在您的 CATCH 块中发出 ROLLBACK TRANSACTION 后,您仍然可以访问 ERROR_PROCEDURE() 等功能,这是您需要在此处执行的操作。看Using TRY...CATCH in Transact SQL中的例子,特别是看“错误处理示例”中的代码。它调用的记录错误的过程 (uspLogError) 在其上方出现了几个示例:

BEGIN CATCH
    -- Call procedure to print error information.
    EXECUTE dbo.uspPrintError;

    -- Roll back any active or uncommittable transactions before
    -- inserting information in the ErrorLog.
    IF XACT_STATE() <> 0
    BEGIN
        ROLLBACK TRANSACTION;
    END

    EXECUTE dbo.uspLogError @ErrorLogID = @ErrorLogID OUTPUT;
END CATCH; 

至于潜在的错误是什么(即当前在您的错误报告中出现错误),如果我不得不猜测,您的消息合同将无法处理 XML 中出现的多行数据。但是我们需要查看合同来确认这一点。

【讨论】:

    【解决方案2】:

    我遇到了同样的错误,因为我使用了与您相同的示例:service broker audit

    我终于设法得到了这条消息的错误,这是安全问题。您有单独的数据库用于审计记录。您的 procAuditSendData 在您的 update/insert/delete 命令的上下文中执行(它使用相同的凭据)。就我而言,来自 procAuditSendData 上下文的用户无权访问审计数据库。为了修复您的错误,您必须在单独的审计数据库中添加该上下文用户并授予他数据读取器和数据写入器的权限。我这样做了,之后一切都像魅力一样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-05-20
      • 1970-01-01
      • 2017-10-08
      • 1970-01-01
      • 2014-04-19
      • 1970-01-01
      相关资源
      最近更新 更多