存储过程捕获异常后返回-4

Hut*_*tch 4 sql-server stored-procedures sql-server-2008-r2

在一个存储过程中,我发现如果它成功插入一条记录,它返回 0。如果它在主键违规(被捕获并忽略)时失败,它返回 -4。-4 来自哪里?

下面是存储过程:

ALTER PROCEDURE [dbo].[pSetUserItemLike] 
        @UserID int
        ,@ItemID int
AS
BEGIN
    SET NOCOUNT ON;

    -- for exception handling
    DECLARE @ErrorMessage nvarchar(max)
    DECLARE @ErrorSeverity int
    DECLARE @ErrorState int

    BEGIN TRY
        INSERT dbo.UserItemLikes
        (
            UserID
            ,ItemID
        )
        VALUES
        (
            @UserID
            ,@ItemID
        )
    END TRY
    BEGIN CATCH
        IF ERROR_NUMBER() <> 2627 -- IGNORE PRIMARY KEY VIOLATION
            SELECT 
                @ErrorMessage = ERROR_MESSAGE() + ' Line ' + cast(ERROR_LINE() as nvarchar(5))
                , @ErrorSeverity = ERROR_SEVERITY()
                , @ErrorState = ERROR_STATE();
            RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState); 
    END CATCH
END
Run Code Online (Sandbox Code Playgroud)

注意:我实际上并没有使用返回值,我只是注意到它,因为 SSMS 的“执行过程”会自动捕获返回值并将其放入一个变量中,但无法弄清楚为什么会发生这种情况。

Aar*_*and 9

虽然我同意即使您违反 PK 也会引发错误(正如另一个答案所暗示的那样,您需要BEGIN/END,因为您的IF条件目前假设所有缩进的内容都将成为IF逻辑的),但这并不是真的回答为什么你得到 -4 特别。

如果您发现错误,您没有明确的返回值。我将借用 Erland Sommarskog 的回答here,其中指出:

但是如果没有RETURN语句,但是在执行过程中出现错误,则返回值为10减去错误的严重程度。除以零是级别 16,因此返回值为 -6。权限错误是典型的 14 级,因此返回值为 -4。

正如您可能猜到的,这不是很有用,但是: 0 是成功,其他一切都是错误。

因此,引发 PK 错误,您会得到:

消息 2627,级别 14,状态 1

根据厄兰公式:

10 - 14 = -4
Run Code Online (Sandbox Code Playgroud)

@ErrorLevel当出现 PK 违规时,您不会获得实际级别,因为当错误消息为 2627 时不会填充这些局部变量(您的IF条件阻止了该特定场景的发生,但不会阻止RAISERROR发生)。如果你能说RAISERROR(NULL,NULL,NULL);那么厄兰的规则适用;这是因为,如果发生了实际错误(我在下面的演示中伪造了这一点),SQL Server 将用ERROR_LEVEL()异常中的实际参数替换该中间参数。我可以证明对于 14 以外的其他错误级别也是如此;例如,Hi/-6在我未能为除以零错误(严重级别为 16)分配变量后,将打印出以下内容:

CREATE PROCEDURE dbo.Test
AS
BEGIN
  SET NOCOUNT ON;
  DECLARE @ErrorLevel INT;

  BEGIN TRY
    SELECT 1/0; -- level 16!
  END TRY
  BEGIN CATCH
    IF ERROR_NUMBER() <> 8134 -- divide by 0
      SET @ErrorLevel = ERROR_SEVERITY();
    RAISERROR('Hi', @ErrorLevel, 1);
  END CATCH
END
GO

DECLARE @rc INT;
EXEC @rc = dbo.Test;
PRINT @rc;
Run Code Online (Sandbox Code Playgroud)

(实际上,在CATCH块内,我找不到覆盖实际异常的真实严重性级别的方法,即使通过硬编码@ErrorLevel为合理和不合理的值也是如此。)

同样来自约翰桑德斯对同一问题的回答:

我的“研究”归功于 SQL Server MVP Tibor Karaszi。他的资料来源是 SQL Server 6.5 的联机丛书。在“Control-Of-Flow Language”, RETURN 下,他发现

"SQL Server 保留 0 表示成功返回,保留 - 1 到 - 99 的负值表示不同的失败原因。如果未提供用户定义的返回值,则使用 SQL Server 值。用户定义的返回状态值不应与 SQL Server 保留的值冲突。值 0 到 -14 当前正在使用。

(同一主题的当前版本不再使用这种语言,但是如果您阅读已故的 Ken Henderson 的书《SQL Server 存储过程、XML 和 HTML 专家指南》,第 39 页他指出 -1 到 -14 表示不同失败级别(并阅读当时版本的联机丛书以查看个别描述,现在有很多好处),并且 -15 到 -99 保留供将来使用。)

最后,厄兰是对的;你得到 -4 或 -6 或 -23 并不重要。如果它不是 0(并且您没有设置显式的非零返回值来指示成功),则您有错误。听起来您只想忽略 PK 违规错误,但是如果您想控制在发生其他非 PK 错误时返回的值,则必须将自己的显式RETURN(x);命令添加到相同的IF一块中。

根据您预期的 PK 违规数量,可能值得考虑手动检查违规情况,而不是盲目地抛出所有内容并让 SQL Server 处理它。引发异常可能会产生大量开销:

进一步演示 -4(和其他值)的来源。创建此过程:

CREATE PROCEDURE dbo.DemoErrors
  @ErrorLevel INT
AS
BEGIN
  RAISERROR(N'foo', @ErrorLevel, 1);
END
GO
Run Code Online (Sandbox Code Playgroud)

现在,如果您使用这些值调用它@ErrorLevel(级别 < 11 将最终返回 0 值,并且 > 19 需要日志等,因此我们将转到 11 -> 18):

SET NOCOUNT ON;
DECLARE @rc INT, @i INT = 11;
WHILE @i <= 18
BEGIN
  EXEC @rc = dbo.DemoErrors @ErrorLevel = @i;
  PRINT @rc;
  SET @i += 1;
END
Run Code Online (Sandbox Code Playgroud)

输出显示返回值始终为10 - @ErrorLevel

Msg 50000, Level 11, State 1, Procedure DemoErrors, Line 25
foo
-1
Msg 50000, Level 12, State 1, Procedure DemoErrors, Line 25
foo
-2
Msg 50000, Level 13, State 1, Procedure DemoErrors, Line 25
foo
- 3
Msg 50000, Level 14, State 1, Procedure DemoErrors, Line 25
foo
-4
Msg 50000, Level 15, State 1, Procedure DemoErrors, Line 25
foo
-5
Msg 50000, Level 16, State 1, Procedure DemoErrors, Line 25
foo
-6
Msg 50000, Level 17, State 1, Procedure DemoErrors, Line 25
foo
-7
Msg 50000, Level 18, State 1, Procedure DemoErrors, Line 25
foo
-8

(如果您以系统管理员身份运行并添加WITH LOGRAISERROR调用中,您将看到 19 产生 -9,依此类推,但在此之上,您将开始断开连接等,因为这些都是严重错误。)