在事务中更新表(通过使用 Service Broker 的审核触发器)
我们已经使用服务代理实现了审计功能,并在需要审计的表上实现了触发器。我们面临的问题是,当我们尝试从事务中更新可审计表时,它会抛出一个错误 -
当前交易不能 已承诺但无法支持 写入日志文件的操作。 回滚事务。
但是,如果我们从可审计表中删除触发器,那么一切都工作得很好。是否无法在事务中更新表(带触发器),或者我们最后是否遗漏了某些内容?
更新事务
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
从触发器内调用
ALTER PROCEDURE [dbo].[procAuditSendData]
存储过程 (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
结束
We have implemented Auditing capability using service broker and have implemented triggers on the tables that need to be audited. The issue we are facing is when we try to update an auditable table from within a transaction, it throws up an error -
The current transaction cannot be
committed and cannot support
operations that write to the log file.
Roll back the transaction.
However, if we remove the trigger from the auditable table, it all works absolutely fine. is it not possible to have a table (with trigger) be updated within a transaction or are we missing something at our end ?
Update Transaction
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
Trigger
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
Stored Proc (procAuditSendData) called from within the trigger
ALTER PROCEDURE [dbo].[procAuditSendData]
(
@AuditedData XML
)
AS
BEGIN
BEGIN TRY
DECLARE @dlgId UNIQUEIDENTIFIER, @dlgIdExists BIT
SELECT @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
END
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在发出
ROLLBACK TRANSACTION
后,您仍然可以访问ERROR_PROCEDURE()
等函数,这是您需要在 CATCH 块中执行的操作。查看在 Transact SQL 中使用 TRY...CATCH 中的示例,特别是查看“错误处理示例”中的代码。它调用的记录错误的过程 (uspLogError
) 上面出现了几个示例:至于底层错误是什么(当前在错误报告中出错),如果我不得不猜测的话可能是您的消息契约无法处理 XML 中出现的多行数据。但我们需要查看合同才能确认这一点。
You can still access the
ERROR_PROCEDURE()
etc functions after you've issued aROLLBACK TRANSACTION
, which is what you need to do here, in your CATCH block. Look at the examples in Using TRY...CATCH in Transact SQL, especially look at the code in "error-handling example". The procedure it calls to log the errors (uspLogError
) appears a couple of samples above it:As to what the underlying error is (that is currently erroring in your error reporting), if I had to guess it would be that the contract for your messages can't cope with multiple rows of data appearing in the XML. But we'd need to see the contract to confirm that.
我遇到了同样的错误,因为我使用了与您相同的示例: 服务代理审核
我终于设法得到此消息的错误,这是安全问题。您有单独的审计记录数据库。您的 procAuditSendData 在更新/插入/删除命令的上下文中执行(它使用相同的凭据)。就我而言,来自 procAuditSendData 上下文的用户无权访问审计数据库。为了修复您的错误,您必须在单独的审计数据库中添加该上下文用户,并授予他数据读取器和数据写入器的权限。我这样做了,之后一切都很顺利。
I had the same error, since I used same example you did: service broker audit
I finally managed to get error of this message and it was the matter of security. You have separate database for audit records. Your procAuditSendData is executed in the context of your update/insert/delete command (it uses same credentials). In my case, user from procAuditSendData context had no rights to access audit database. In order to fix your error you have to add that context user on your separate audit database and grant him the rights of datareader and datawriter. I did this and after that everything worked like a charm.