【问题标题】:Trigger Error: The current transaction cannot be committed and cannot support operations that write to the log file触发错误:当前事务无法提交,无法支持写入日志文件的操作
【发布时间】:2013-04-12 20:29:02
【问题描述】:

所以当 sp_SomeProc 尝试执行无效的 sql 语句时,我从 SQL Server 收到以下错误消息。我得到错误:

The current transaction cannot be committed and cannot support operations that write to the log file. 

关于我做错了什么有什么想法吗? (这只是我为模拟问题而创建的一个示例,所以请不要说“你为什么这样做?”、“这有安全隐患”等。)


所以我的桌子看起来像:

CREATE TABLE tSOMETABLE
(  
    RecID INT NOT NULL IDENTITY(1,1)
    Val VARCHAR(20),
CONSTRAINT [PK_tSOMETABLE] PRIMARY KEY CLUSTERED 
(
    RecID ASC
)
)

所以在我的触发器中我有:

CREATE TRIGGER [dbo].[TR_tSOMETABLE_INSERT]     
    ON [dbo].[tSOMETABLE]   
    FOR INSERT  
AS      
SET NOCOUNT ON  
BEGIN   
         BEGIN
            SELECT * INTO #temp FROM INSERTED

            WHILE EXISTS (SELECT 1 FROM #temp)
            BEGIN
                DECLARE @RecID INT      
                SELECT @RecID = RecID
                FROM #temp t
                EXEC dbo.sp_SomeProc @EventType = 'ON INSERT', @RecID = @RecID
                DELETE #temp WHERE @RecID = RecID
            END         
        END   
END

现在 sp_SomeProc 的代码如下所示:

CREATE PROC sp_SomeProc 
(
    @EventType VARCHAR(50),
    @RecID INT,
    @Debug BIT = 0
)
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @ProcTable TABLE 
    (
        RecID INT NOT NULL IDENTITY(1,1),
        Cmd VARCHAR(MAX)
    )

    INSERT INTO @ProcTable(Cmd)
      SELECT 'EXEC sp_who'
      UNION
      SELECT 'EXEC sp_SomeStoredProcThatDoesntExist'


    DECLARE  @RecID INT  
    SELECT @RecID = MIN(RecID) FROM @ProcTable
    WHILE @RecID IS NOT NULL
    BEGIN  
        DECLARE @sql VARCHAR(MAX)
        SELECT @sql = cmd FROM @ProcTable WHERE RecID = @RecID
        IF @Debug = 1
            PRINT @sql
        ELSE
            BEGIN
                BEGIN TRY      
                    EXEC(@sql)
                END TRY
                BEGIN CATCH
                    DECLARE @Msg VARCHAR(MAX), @ErrorNumber INT, @ErrorSeverity INT, @ErrorState int, @ErrorProcedure nvarchar(256), @ErrorLine int, @ErrorMessage nvarchar(MAX)
                    SELECT @Msg = 'Failed While Executing: ' + @sql  
                    SELECT @ErrorNumber = ERROR_NUMBER(), @ErrorSeverity = ERROR_SEVERITY(), @ErrorState = ERROR_STATE(), @ErrorProcedure = ERROR_PROCEDURE(), @ErrorLine = ERROR_LINE(), @ErrorMessage = ERROR_MESSAGE()
                    -- DO SOME MORE STUFF HERE AND THEN ...
                    RAISERROR(@ErrorMessage, @ErrorSeverity, @ErrorState)
                END CATCH 
            END
        SELECT @RecID = MIN(RecID) FROM @ProcTable WHERE RecID > @RecID
    END  
END

所以为了测试我尝试:

INSERT INTO tSOMETABLE(Val)
SELECT 'Hello'

【问题讨论】:

  • 回归基础。触发器实际上试图做什么?为什么不能使用 inserted 在触发器中作为基于集合的操作来执行此操作,而不是逐行循环并为每一行执行单独的复杂存储过程?
  • 不能,触发器需要根据进入该行的数据为插入的每一行执行一些存储过程。它需要动态执行sql,不能作为集合操作完成。我无法控制它为每一行执行的存储过程。在完整的实现中,有一个表决定为每一行 INSERTED 运行哪个 sql(但这与这个问题无关)

标签: sql sql-server sql-server-2008 sql-server-2008-r2


【解决方案1】:

当您在事务中使用 try/catch 块时会发生此错误。让我们考虑一个简单的例子:

SET XACT_ABORT ON

IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)
    INSERT INTO #t (i) VALUES (3)
    INSERT INTO #t (i) VALUES (1) -- dup key error, XACT_ABORT kills the batch
    INSERT INTO #t (i) VALUES (4) 

COMMIT  TRAN
SELECT * FROM #t

当第四次插入导致错误时,批处理被终止并且事务回滚。到目前为止没有任何惊喜。

现在让我们尝试使用 TRY/CATCH 块来处理该错误:

SET XACT_ABORT ON
IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)
    BEGIN TRY
        INSERT INTO #t (i) VALUES (3)
        INSERT INTO #t (i) VALUES (1) -- dup key error
    END TRY
    BEGIN CATCH
        SELECT ERROR_MESSAGE()
    END CATCH  
    INSERT INTO #t (i) VALUES (4)
    /* Error the Current Transaction cannot be committed and 
    cannot support operations that write to the log file. Roll back the transaction. */

COMMIT TRAN
SELECT * FROM #t

我们发现了重复键错误,但除此之外,我们的情况并没有好转。我们的批处理仍然被终止,我们的事务仍然被回滚。原因其实很简单:

TRY/CATCH 块不影响事务。

由于 XACT_ABORT ON,一旦发生重复键错误,事务就注定失败。它已经完成了。它受了致命伤。它被击穿了心脏……错误是罪魁祸首。 TRY/CATCH 给 SQL Server 带来了一个坏名声。 (对不起,忍不住了)

换句话说,它将从不提交并且将总是回滚。 TRY/CATCH 块所能做的就是打破尸体的坠落。我们可以使用 XACT_STATE() 函数来查看我们的事务是否可提交。如果不是,唯一的选择是回滚事务。

SET XACT_ABORT ON -- Try with it OFF as well.
IF object_id('tempdb..#t') IS NOT NULL
    DROP TABLE #t
CREATE TABLE #t (i INT NOT NULL PRIMARY KEY)

BEGIN TRAN
    INSERT INTO #t (i) VALUES (1)
    INSERT INTO #t (i) VALUES (2)

    SAVE TRANSACTION Save1
    BEGIN TRY
        INSERT INTO #t (i) VALUES (3)
        INSERT INTO #t (i) VALUES (1) -- dup key error
    END TRY
    BEGIN CATCH
        SELECT ERROR_MESSAGE()
        IF XACT_STATE() = -1 -- Transaction is doomed, Rollback everything.
            ROLLBACK TRAN
        IF XACT_STATE() = 1 --Transaction is commitable, we can rollback to a save point
            ROLLBACK TRAN Save1
    END CATCH  
    INSERT INTO #t (i) VALUES (4)

IF @@TRANCOUNT > 0
    COMMIT TRAN
SELECT * FROM #t

触发器总是在事务的上下文中执行,因此如果您可以避免在其中使用 TRY/CATCH,事情就会简单得多。

为了解决您的问题,CLR 存储过程可以在单独的连接中连接回 SQL Server 以执行动态 SQL。您可以获得在新事务中执行代码的能力,并且错误处理逻辑在 C# 中既易于编写又易于理解。

【讨论】:

  • 如果我遇到同样的错误并且我没有参与交易,会发生什么?我正在使用 try/catch 来尝试解析不同的日期。 Excel 使用“序列号”,但其他使用普通日期。另外,如果它是动态 sql 和事务,如果被捕获,错误是否会汇总到事务中?
  • 即使我没有使用 TRANSACTIONS,我也会收到此错误,我正在通过链接服务器连接到不同的数据库实例。
猜你喜欢
  • 2011-11-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多