【发布时间】: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