SQL Server 正常退出存储过程并避免开始/COMMIT不匹配

sdnqo3pr  于 2022-12-03  发布在  其他
关注(0)|答案(1)|浏览(142)

在VBA应用程序中捕获SQL错误时遇到一些问题之后,我重新设计了存储过程,以便在发生错误时,返回值是错误代码,输出变量包含错误消息。我不在catch块中重新抛出错误。由于没有更好的术语,我将称之为“优雅退出”。它使客户端的工作变得更容易,但现在当嵌套存储过程触发的触发器回滚事务时,我遇到了一个问题。
以下面的示例为例。TEST_INNER_PROC@@TRANCOUNT为1开始,执行引发触发器的插入操作,该操作回滚事务,当TEST_INNER_PROC退出时,它抛出错误
266:EXECUTE之后的事务计数指示开始和COMMIT语句的数目不匹配
通常,我会将这两个过程模式化为相同的;我在这里对它们进行了简化。内部过程不会尝试启动事务(这不会有什么不同),外部过程会重新抛出错误,这样我就可以看到打印的错误信息。通常,我会通过返回代码和@ERR_MSG输出变量将错误代码返回给客户端。
我喜欢@gbn的模式:嵌套存储过程包含TRY CATCH ROLLBACK模式?但是,如果回滚发生在触发器中,它似乎不适合我的“优雅退出”。我也不确定Rusanu's pattern是否也适合它。

  1. CREATE TABLE TEST (
  2. COL1 INT
  3. )
  4. GO
  5. CREATE TRIGGER TEST_TRIGGER
  6. ON TEST FOR INSERT
  7. AS
  8. BEGIN TRY
  9. THROW 50001, 'TEST Trigger produced an error.', 1
  10. END TRY
  11. BEGIN CATCH
  12. IF @@TRANCOUNT > 0 AND XACT_STATE()<>0
  13. ROLLBACK TRAN;
  14. THROW
  15. END CATCH
  16. GO
  17. CREATE PROC TEST_INNER_PROC
  18. AS
  19. SET NOCOUNT, XACT_ABORT ON
  20. DECLARE @RTN INT = 0
  21. BEGIN TRY
  22. INSERT TEST (COL1) VALUES (1)
  23. END TRY
  24. BEGIN CATCH
  25. IF @@TRANCOUNT > 0 AND XACT_STATE()<>0
  26. ROLLBACK TRAN
  27. SET @RTN = ERROR_NUMBER();
  28. --THROW
  29. END CATCH
  30. RETURN @RTN
  31. GO
  32. CREATE PROC TEST_OUTER_PROC
  33. AS
  34. SET NOCOUNT, XACT_ABORT ON
  35. DECLARE @RTN INT = 0
  36. BEGIN TRY
  37. BEGIN TRAN
  38. EXEC @RTN = TEST_INNER_PROC
  39. IF @RTN <> 0 THROW 50000, 'Execution of TEST_INNER_PROC produced an error.', 1
  40. COMMIT TRAN
  41. END TRY
  42. BEGIN CATCH
  43. IF @@TRANCOUNT > 0 AND XACT_STATE()<>0
  44. ROLLBACK TRAN;
  45. THROW
  46. END CATCH
  47. GO
  48. EXEC TEST_OUTER_PROC
  49. GO
  50. DROP TABLE TEST
  51. DROP PROC TEST_OUTER_PROC
  52. DROP PROC TEST_INNER_PROC
  53. GO

以上代码将产生:
消息266,级别16,状态2,程序TEST_INNER_PROC,第63行
EXECUTE之后的事务计数指示开始和COMMIT语句的数目不匹配。上一个计数= 1,当前计数= 0。
但是如果您取消注解TEST_INNER_PROC中的“THROW”语句,它将抛出:
消息50001,级别16,状态1,程序TEST_TRIGGER,第69行
TEST触发程序产生错误。
这是我想在TEST_OUTER_PROC中处理的错误。
有没有可能使用“优雅地退出”的存储过程,将错误代码和错误消息作为变量返回,并避免BEGINCOMMIT语句的不匹配数量?

evrscar2

evrscar21#

您可以将所有错误存储在表中并从中选择它们

  1. CREATE TABLE LOG_ERROR (
  2. SPID INT DEFAULT @@SPID
  3. ,DATE DATETIME DEFAULT GETDATE()
  4. ,ERROR_NUMBER INT DEFAULT ERROR_NUMBER())

在您的程序中:

  1. begin try
  2. --code...
  3. end try
  4. begin catch
  5. INSERT LOG_ERROR DEFAULT VALUES;
  6. throw
  7. end catch

程序执行后:

  1. SELECT * FROM LOG_ERROR
  2. WHERE SPID = @@SPID
  3. AND DATE > CONVERT(DATE,GETDATE()) --Errors from today

更新:创建一个视图,这样您也可以获得文本:

  1. CREATE VIEW VW_LOG_ERROR AS
  2. SELECT
  3. E.*
  4. ,M.TEXT
  5. FROM LOG_ERROR E
  6. JOIN SYS.MESSAGES M WITH(NOLOCK) ON E.ERROR_NUMBER = M.MESSAGE_ID
  7. JOIN SYS.SYSLANGUAGES L
  8. ON M.LANGUAGE_ID = L.MSGLANGID
  9. AND L.LANGID = @@LANGID
  10. WHERE
  11. SPID = @@SPID
  12. AND DATE > CONVERT(DATE,GETDATE()) --Errors from today
展开查看全部

相关问题