制作回滚豁免插入

时间:2018-09-25 21:01:38

标签: sql-server tsql transactions sql-server-2016-localdb

这是一个奇怪的问题:是否可以在表中进行免回滚插入?

这是场景: 我们有一个可以做事的触发器。

有时,此触发器将调用RAISERRROR()。然后外部事务回滚。

但是,在触发器中,我想将值插入到日志记录表中,并且在回滚期间(如果愿意的话)可以免除事务处理。

3 个答案:

答案 0 :(得分:1)

香蕉捕获

您可以使用try-catch捕获异常并将其传递/抛出外部范围,该范围必须存在相同的内容。顺便说一下,这允许您收集call-stack。如果仅通过storedprocs工作,则可以实施此解决方案。每个proc必须具有这样的模式:

begin try
end try
begin catch
   if @@trancount > 0
     rollback

   insert into <log> (...)
   values (...)

   throw
end catch

因此最顶层的过程将成功向日志表中插入一行。

缺点:

  • 具有较高的层次结构-太多的“虚拟”插入无法回滚
  • 如果出现以下情况,
  • 仍然无法记录任何内容:
    • 严重程度太高
    • SP不符合此模式
    • 有外部/“客户端”交易管理

优点:

  • 不够完善,但仍然可行,并且实施起来不太困难。

反馈:

  • 我确实在小型项目的生产服务器上使用了这种方法,并收集了调用堆栈,等等。这是一个具有约50-100个在线用户的后台办公软件,运行着一定数量的机器人,远远不是“高负载”。它运行良好,有助于解决许多问题。开销并没有打扰任何人。

难以捉摸的强盗

您可以构建一个确实插入到日志表中的CLR组件,仅此而已...但是!可以指定一个单独的连接,以通过该单独的连接使此程序集与db一起使用。这意味着-在单独的范围内。因此,无论在何种事务范围内,都将从事务内调用此程序集的方法,但是将执行该程序集。

所以,而不是:

using(SqlConnection connection = new SqlConnection("context connection=true"))

只需指定常规连接字符串即可。调用之后-抛出带有修改后的ERROR_STATUS的异常,以避免再次记录相同的错误。

begin try
end try
begin catch
   if @@trancount > 0
     rollback

   if @@ERRROR_STATUS != @done_with_logging
     exec asm.log(...)

   raiserror @err_msg, @severity, @done_with_logging
end catch

缺点:

  • 可能导致连接池问题
  • 有点琐碎的插入方式
  • 可能会导致与建立连接和程序集本身有关的其他问题(+权限,所有权等)

优点:

  • 这个想法有点令人兴奋
  • 可以登录到文件而不是db

反馈:

  • 我不记得我是否在产品上使用了这种方法(尽管我记得这是最近的经验……或者只是为了测试目的而尝试实现)

敲,敲,管理员

这个简单的语句(实际上只是附加选项WITH LOG)会将您想要的任何错误消息写入SQL SERVER事件日志:

RAISERROR(...) WITH LOG

这不是应该使用SQL SERVER日志的方法,但这是记录重要内容(解决问题)的最快方法。可以在SSMS代理的窗口中查看已记录的事件。

缺点:

  • 可能有权限方面的考虑
  • 搜索质量差和零自定义功能
  • dba会讨厌您的(当然,如果他曾经检查过服务器日志)

优点:

  • 单行实施

反馈:

  • 我只在一家公司遇到过这种登录方式。我猜想,一段时间以前,它被一些开发人员用来捕获细微的错误,但后来在代码中流传开来,并成为一种“标准”。因此,一段时间后,几乎不可能在事件日志中找到特定的内容了……实际上,只有很少的同事可以访问产品服务器的日志。因此,这实际上是a)有害b)无用的。所以我一有空就从系统的受管部分中删除了它。

飞行荷兰人

{一个关于基于DML触发器构建系统的基于意见的讨论的场所}

在我看来,您没有在项目中使用存储的proc而是执行临时查询。如果您的后端应用程序带有ORM或类似的东西,请使用它编写日志。而且,也许此后端应用程序是在触发器内部执行操作的更好场所。

如果您的项目是不带appserver / backend应用程序的客户端-服务器应用程序,而您仅有的是即席查询和触发器,则没有太多数据要记录。没有调用堆栈(在服务器端)。而且很难确定您(用户,应用程序)是如何发生此特定异常的。因此,在这种情况下,登录客户端可能会更有用。

答案 1 :(得分:0)

如果创建一个表变量然后插入到该变量中-它将不会包含在回滚中,因此可以将其内容转储到永久表中。

例如

declare @tab table (msg varchar(255))

BEGIN TRY

    BEGIN TRANSACTION

    select 1+2

    INSERT @tab values ('first step complete')

    SElect 1/0

    INSERT @tab values ('2nd step complete')

    COMMIT

END TRY

BEGIN CATCH

    ROLLBACK

    SELECT * FROM @tab
END CATCH

答案 2 :(得分:0)

过段时间再写。您可以使用代替触发器:

create table t
(
i int,
s varchar(10)
)
go

create table t2
(
i int,
s varchar(10)
)
go

create table tlog
(
i int,
s varchar(10)
)
go

alter trigger tt on t
INSTEAD OF INSERT
AS
BEGIN
    rollback transaction
    raiserror ('something went wrong', 16, 2)
    insert tlog (i,s)
    select i, s
    from inserted
END
go

truncate table t
truncate table t2
truncate table tlog
go

select * from t
select * from t2
select * from tlog
go

begin transaction
insert t2 (i,s) values (1, 'abc')
insert t (i,s) values (1, 'abc')
commit transaction
go

select * from t
select * from t2
select * from tlog

给出以下输出:

我是


(0 行受影响)

我是


(0 行受影响)

我是


(0 行受影响)

(1 行受影响) 消息 50000,级别 16,状态 2,程序 tt,第 7 行 出了点问题

(1 行受影响) 消息 3609,级别 16,状态 1,第 4 行 事务在触发器中结束。该批次已中止。 我是


(0 行受影响)

我是


(0 行受影响)

我是


1 abc

(1 行受影响)