使用存储过程时预测致命错误

Posted

技术标签:

【中文标题】使用存储过程时预测致命错误【英文标题】:Anticipating fatal errors when using stored procedures 【发布时间】:2015-02-28 13:34:38 【问题描述】:

请看下面的代码:

ALTER PROCEDURE GetPerson

AS
BEGIN TRANSACTION;
BEGIN TRY
    DECLARE @TestVariable1 AS INT;
    DECLARE @TestVariable2 AS INT;
    SET @TestVariable1 = 1;
    SET @TestVariable2 = 0;
    DECLARE @TestVariable3 AS INT;
    SET @TestVariable3 = @TestVariable1 / @TestVariable2;
    PRINT 'transaction committed';
END TRY
BEGIN CATCH
    ROLLBACK;
    PRINT 'transaction rolled back';
END CATCH

我可以使用以下命令运行它:

EXEC GetPerson

事务成功回滚,因为你不能除我的零。

现在看下面的代码:

ALTER PROCEDURE GetPerson

AS
BEGIN TRANSACTION;
BEGIN TRY
    CREATE TABLE #Test
    (
        test INT
    );
    SELECT TOP 1 *
    FROM   #Test;
    SELECT TOP 1 *
    FROM   person.person;
    DROP TABLE #Test;
    SELECT TOP 1 *
    FROM   #Test;
    PRINT 'transaction committed';
END TRY
BEGIN CATCH
    ROLLBACK;
    PRINT 'transaction rolled back';
END CATCH

如果我运行相同的评论:

EXEC GetPerson

然后我得到一个错误:

消息 208,级别 16,状态 0,过程 GetPerson,第 11 行 无效的对象名称“#Test”。

消息 266,级别 16,状态 2,过程 GetPerson,第 11 行 EXECUTE 之后的事务计数表明 BEGIN 和 COMMIT 语句的数量不匹配。先前计数 = 0,当前计数 = 1

我了解并非所有错误都被 TRY 子句捕获,如下所述:https://msdn.microsoft.com/en-gb/library/ms178592.aspx

当 RAISERROR 在 TRY 块中以 11 或更高的严重性运行时,它会将控制权转移到关联的 CATCH 块

我的问题是:您如何处理这些问题?我知道在我的代码中,尝试从不再存在的临时表中进行选择是一个明显的开发人员错误。但是,如果还有其他我应该处理的错误,我会徘徊。

在调用存储过程时这样做是否可以接受:

EXECUTE GetPerson ;

IF @@trancount >= 1
ROLLBACK;

或者我可以这样做:

EXEC GetPerson
    IF @@trancount >= 1
    INSERT INTO dbLog ("Trancount greater than 1 after running GetPerson")

【问题讨论】:

FWIW, this 可能会提供一些关于事务和错误处理的额外见解。 【参考方案1】:

错误 208 是一个编译错误。您的代码永远不会运行,因为它甚至无法编译。这与要求 C# 程序捕获编译错误相同。

此外,如果您想要一个正确的 try/catch 块来处理异常和事务,请参阅Exception handling and nested transactions:

create procedure [usp_my_procedure_name]
as
begin
    set nocount on;
    declare @trancount int;
    set @trancount = @@trancount;
    begin try
        if @trancount = 0
            begin transaction
        else
            save transaction usp_my_procedure_name;

        -- Do the actual work here

lbexit:
        if @trancount = 0   
            commit;
    end try
    begin catch
        declare @error int, @message varchar(4000), @xstate int;
        select @error = ERROR_NUMBER(), @message = ERROR_MESSAGE(), @xstate = XACT_STATE();
        if @xstate = -1
            rollback;
        if @xstate = 1 and @trancount = 0
            rollback
        if @xstate = 1 and @trancount > 0
            rollback transaction usp_my_procedure_name;

        raiserror ('usp_my_procedure_name: %d: %s', 16, 1, @error, @message) ;
    end catch   
end

显然这不会处理语法/绑定错误(如 208),但至少会处理正确的过程。请记住,在 CATCH 块中检查 XACT_STATE()强制。考虑像 1205(死锁)这样的错误,其中 CATCH 块在事务回滚后运行。您还需要尊重和处理调用者的交易。

【讨论】:

谢谢+1。我有点困惑。我将解释问题的原因。我有一个需要大约两个小时才能运行的存储过程。在第一行,它创建了一个临时表。最近在运行大约十分钟后,它失败并出现错误:“无效的对象名称'#TableName”。因此这表明它确实尝试运行?

以上是关于使用存储过程时预测致命错误的主要内容,如果未能解决你的问题,请参考以下文章

oracle创建存储过程时,提示错误是:错误(5,18): PL/SQL: ORA-00947: 没有足够的值?代码如下:

执行存储过程时参数未提供错误

当我尝试使用雪花存储过程执行更改命令时给出错误?

oracle创建存储过程时,提示错误是:错误(5,18): PL/SQL: ORA-00947: 没有足够的值?代码如下:

创建存储过程时的语法错误

执行不返回结果的存储过程时引发错误