简体   繁体   English

下级过程失败时exec into(insert into from storage procedure)后出现神秘错误:“当前事务无法提交”

[英]Mysterious error after exec into (insert into from stored procedure) when subordinate procedure fails: "current transaction cannot be committed"

I encountered a problem using T-SQL / SQL server (2017/140):我在使用 T-SQL/SQL server (2017/140) 时遇到了一个问题:

In my scenario there are a number of stored procedures (= worker procedures), which in turn perform different tasks and at the end output the result with a select statement.在我的场景中,有许多存储过程(= 工作过程),它们依次执行不同的任务,最后用 select 语句输出结果。

A superordinate procedure (= runner procedure) calls the worker procedures one after the other and collects their result in a table using an insert-into statement.上级过程(= runner 过程)一个接一个地调用工作过程,并使用 insert-into 语句将它们的结果收集到一个表中。

Processing within the worker procedures is secured with try-/catch statements so that the runner procedure is not aborted if one of the worker procedures should fail.工作过程中的处理使用 try-/catch 语句进行保护,以便在工作过程之一失败时不会中止运行过程。 It is also ensured that the worker procedures always return a result, even if this procedure encounters an error during processing.还确保工作过程始终返回结果,即使此过程在处理过程中遇到错误。

However, the runner procedure encounters an error if an error has occurred in a worker procedure, although this error has already been intercepted and handled in the worker procedure.但是,如果在工作过程中发生错误,运行程序会遇到错误,尽管该错误已经在工作过程中被拦截和处理。 The error message is: „The current transaction cannot be committed and cannot support operations that write to the log file.错误消息是:“当前事务无法提交,无法支持写入日志文件的操作。 Roll back the transaction.“ As you can see from my example script, no explicit transactions are used there.回滚事务。“正如您从我的示例脚本中看到的那样,那里没有使用显式事务。

The problem only occurs when the results of the worker procedures are transferred to a table with a Select-Into statement.仅当工作过程的结果传输到带有 Select-Into 语句的表时,才会出现此问题。 As an example I have created another runner procedure, which only executes the worker procedures, but does not transfer their results to a table.作为一个例子,我创建了另一个运行程序,它只执行工作程序,但不将它们的结果传输到表中。 In this case there will be no errors.在这种情况下,不会有错误。

CREATE SCHEMA bugchase;
GO

DROP PROCEDURE IF EXISTS bugchase.worker_1;
DROP PROCEDURE IF EXISTS bugchase.worker_2;
DROP PROCEDURE IF EXISTS bugchase.runner_selectOnly;
DROP PROCEDURE IF EXISTS bugchase.runner_selectAndInsert;
GO

CREATE PROCEDURE bugchase.worker_1
AS
BEGIN
    -- this worker just returns a value
    SELECT
        'Result Worker 1';
END;
GO

CREATE PROCEDURE bugchase.worker_2
AS
BEGIN
    -- this worker encounters an error while processing.
    -- the error will be catched and some data will be returned

    DECLARE @result INT;

    BEGIN TRY
        -- this will force an error, because 'ABCD' could not be casted as float
        SET @result = CAST('ABCD' AS FLOAT);

    END TRY
    BEGIN CATCH
    -- just catch, don't do anything else

    END CATCH;

    SELECT
        'Result Worker 2';
END;
GO

CREATE PROC bugchase.runner_selectAndInsert
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @result AS TABLE (value NVARCHAR(64));

    -- exec worker_1 (no error will be thrown inside worker_1)
    PRINT 'Select&Insert: Worker 1 start';
    INSERT INTO @result (value)
    EXEC bugchase.worker_1;
    PRINT 'Select&Insert: Worker 1 end';

    -- exec worker_2 (will throw and catch an error inside)
    PRINT 'Select&Insert: Worker 2 start';
    INSERT INTO @result (value)
    EXEC bugchase.worker_2;
    PRINT 'Select&Insert: Worker 2 end';

END;
GO

CREATE PROC bugchase.runner_selectOnly
AS
BEGIN
    SET NOCOUNT ON;

    -- exec worker_1 (no error will be thrown inside worker_1)
    PRINT 'SelectOnly: Worker 1 start';
    EXEC bugchase.worker_1;
    PRINT 'SelectOnly: Worker 1 end';

    -- exec worker_2 (will throw and catch an error inside)
    PRINT 'SelectOnly: Worker 2 start';
    EXEC bugchase.worker_2;
    PRINT 'SelectOnly: Worker 2 end';


END;
GO

BEGIN TRY
    -- because all errors are catched within the worker-procedures, there should no error occur by this call
    EXEC bugchase.runner_selectAndInsert;
END TRY
BEGIN CATCH
    -- indeed an error will occur while running worker 2
    PRINT CONCAT('Select&Insert: ERROR:', ERROR_MESSAGE());
END CATCH;
GO

BEGIN TRY
    -- for demonstration only, this will run as expected
    EXEC bugchase.runner_selectOnly;
END TRY
BEGIN CATCH
    -- No error will occur
    PRINT CONCAT('SelectOnly: ERROR:', ERROR_MESSAGE());
END CATCH;
GO

DROP PROCEDURE bugchase.worker_2;
DROP PROCEDURE bugchase.worker_1;
DROP PROCEDURE bugchase.runner_selectOnly;
DROP PROCEDURE bugchase.runner_selectAndInsert;
DROP SCHEMA bugchase;

The script above will show the following result:上面的脚本将显示以下结果:

Select&Insert: Worker 1 start
Select&Insert: Worker 1 end
Select&Insert: Worker 2 start
Select&Insert: ERROR:
The current transaction cannot be committed and cannot support operations that write to the log file.
Roll back the transaction.

No error occurs, when the results are not stored into a table:当结果未存储到表中时,不会发生错误:

SelectOnly: Worker 1 start
SelectOnly: Worker 1 end
SelectOnly: Worker 2 start
SelectOnly: Worker 2 end

As Anton mentioned in the comments, this is a side effect of the autocommit functionality of SQL Server which is mentioned in the documentation for SET IMPLICIT_TRANSACTIONS .正如 Anton 在评论中提到的,这是SET IMPLICIT_TRANSACTIONS文档中提到的 SQL Server 自动提交功能的副作用。 Basically, with IMPLICIT_TRANSACTIONS OFF (the default), there are a bunch of statements that are automatically wrapped in an unseen transaction.基本上,当IMPLICIT_TRANSACTIONS OFF (默认)时,有一堆语句会自动包装在一个看不见的事务中。 INSERT is one of them, so anything that happens within the INSERT INTO EXEC is in one of these autocommit transactions. INSERT就是其中之一,因此INSERT INTO EXEC中发生的任何事情都在这些自动提交事务之一中。 You can see this for yourself by printing or selecting @@trancount within one of the worker procs.您可以通过在其中一个工作进程中打印或选择 @@trancount 来亲眼看到这一点。

This other question has some great explanations of why the uncommittable transaction occurs even though you are catching the error. This other question对为什么即使您捕获错误也会发生不可提交的事务有一些很好的解释。

In your case, you can work around this by rewriting the code to return data from the worker procs without using insert into.在您的情况下,您可以通过重写代码以从工作进程返回数据而不使用插入来解决此问题。 One simple option would be to replace your table variable in the runner proc with a temp table, then insert into that temp table within the worker procs instead of just selecting the result set.一个简单的选择是用临时表替换 runner proc 中的表变量,然后插入到 worker procs 中的临时表中,而不仅仅是选择结果集。

eg runner_selectAndInsert would become:例如 runner_selectAndInsert 会变成:

CREATE PROC bugchase.runner_selectAndInsert
AS
BEGIN
    SET NOCOUNT ON;

    CREATE TABLE #result (value NVARCHAR(64));

    -- exec worker_1 (no error will be thrown inside worker_1)
    PRINT 'Select&Insert: Worker 1 start';
    EXEC bugchase.worker_1;
    PRINT 'Select&Insert: Worker 1 end';

    -- exec worker_2 (will throw and catch an error inside)
    PRINT 'Select&Insert: Worker 2 start';
    EXEC bugchase.worker_2;
    PRINT 'Select&Insert: Worker 2 end';

END;

and worker_1 would become: worker_1 将变成:

CREATE PROCEDURE bugchase.worker_1
AS
BEGIN
    -- this worker just returns a value
    INSERT INTO #result (value)
    SELECT
        'Result Worker 1';
END;

Some more options for passing data between procs like this are fleshed out by Erland Sommarskog here . Erland Sommarskog在这里充实了一些在 procs 之间传递数据的更多选项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM