经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库运维 » MS SQL Server » 查看文章
sql server中嵌套事务*
来源:cnblogs  作者:zhangfen1991  时间:2018/11/9 18:22:01  对本文有异议

转自 https://www.cnblogs.com/guanjie20/archive/2013/02/17/2914488.html

我们在写事务时经常遇到的问题如下:

消息 266,级别 16,状态 2,过程 sp1,第 0 行
EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0。
消息 3903,级别 16,状态 1,过程 sp2,第 15 行
ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION
 

如果这只是一个单独的事务引起的,那么很好解决,我们只要检查下是否遗漏了匹配的BEGIN tran 和 COMMIT tran即可,但是如果2个存储过程都是用事务写的,那么就即使每个存储过程的事务写法都正常,也会报这个错误,

这是因为只要子事务里有回滚语句:如ROLLBACK      那么全局的@@TRANCOUNT被直接置为0了,导致父事务提交时发现 @@TRANCOUNT=0  报错 ,sql server会认为当前不存在任何事务,在父存储过程中任何的COMMIT TRAN或

ROLLBACK 语句都会找不到它对应的 BEGIN TRAN   

下面我们用一个实例来看下:

假设有一张表,ID为非自增主键

  1. USE [TestDB]
  2. GO
  3.  
  4. /****** Object: Table [dbo].[test] Script Date: 02/17/2013 15:44:35 ******/
  5. SET ANSI_NULLS ON
  6. GO
  7.  
  8. SET QUOTED_IDENTIFIER ON
  9. GO
  10.  
  11. SET ANSI_PADDING ON
  12. GO
  13.  
  14. CREATE TABLE [dbo].[test](
  15. [ID] [bigint] NOT NULL,
  16. [UserID] [bigint] NULL,
  17. [Name] [varchar](50) NULL,
  18. CONSTRAINT [PK_Table_1] PRIMARY KEY CLUSTERED
  19. (
  20. [ID] ASC
  21. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  22. ) ON [PRIMARY]
  23.  
  24. GO
  25.  
  26. SET ANSI_PADDING OFF
  27. GO

我们常规的写一个插入的子存储过程如下:

  1. USE [TestDB]
  2. GO
  3. /****** Object: StoredProcedure [dbo].[innertranv1] Script Date: 02/17/2013 15:46:46 ******/
  4. SET ANSI_NULLS ON
  5. GO
  6. SET QUOTED_IDENTIFIER ON
  7. GO
  8.  
  9. --内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务
  10. ALTER PROCEDURE [dbo].[innertranv1]
  11. @ID BIGINT ,
  12. @UserID BIGINT ,
  13. @Name VARCHAR(50)
  14. AS
  15. BEGIN
  16. SET XACT_ABORT ON
  17. BEGIN TRAN
  18. IF(EXISTS(SELECT TOP 1 * FROM dbo.test WHERE ID=@ID))
  19. BEGIN
  20. ROLLBACK
  21. RETURN 0 ;
  22. END
  23. --业务逻辑开始
  24. INSERT dbo.test
  25. ( ID, UserID, Name)
  26. VALUES ( @ID,
  27. @UserID,
  28. @Name
  29. )
  30. --业务逻辑结束
  31. IF @@error <> 0
  32. BEGIN
  33. ROLLBACK
  34. RETURN 0;
  35. END
  36. COMMIT
  37.      SET XACT_ABORT OFF;
  38.      RETURN 1 ;
  39. END

调用的父存储过程如下:

  1. USE [TestDB]
  2. GO
  3. /****** Object: StoredProcedure [dbo].[outertranv2] Script Date: 02/17/2013 16:09:09 ******/
  4. SET ANSI_NULLS ON
  5. GO
  6. SET QUOTED_IDENTIFIER ON
  7. GO
  8.  
  9. -- =============================================
  10. -- Author: <Author,,Name>
  11. -- Create date: <Create Date,,>
  12. -- Description: <外层存储过程>
  13. -- =============================================
  14. ALTER PROCEDURE [dbo].[outertranv2]
  15. @ID BIGINT ,
  16. @UserID BIGINT ,
  17. @Name VARCHAR(50)
  18. AS
  19. BEGIN TRAN
  20. DECLARE @result INT
  21. EXEC @result = innertranv1 @ID =@ID, @UserID =@UserID, @Name = @Name
  22. IF ( @result <= 0 )
  23. BEGIN
  24. ROLLBACK TRAN ;
  25. RETURN ;
  26. END
  27. COMMIT TRAN

我们执行父存储过程:

  1. USE [TestDB]
  2. GO
  3.  
  4. DECLARE @return_value int
  5.  
  6. EXEC @return_value = [dbo].[outertranv2]
  7. @ID = 0,
  8. @UserID = 0,
  9. @Name = N'0'
  10.  
  11. SELECT 'Return Value' = @return_value
  12.  
  13. GO

第一次提交正常,再次执行就会出现如下错误:

  1. 消息 266,级别 16,状态 2,过程 innertranv1,第 0
  2. EXECUTE 后的事务计数指示 BEGIN COMMIT 语句的数目不匹配。上一计数 = 1,当前计数 = 0
  3. 消息 3903,级别 16,状态 1,过程 outertranv2,第 18
  4. ROLLBACK TRANSACTION 请求没有对应的 BEGIN TRANSACTION

如何解决?我们修改子存储过程如下:

  1. USE [TestDB]
  2. GO
  3. /****** Object: StoredProcedure [dbo].[innertran] Script Date: 02/17/2013 16:26:26 ******/
  4. SET ANSI_NULLS ON
  5. GO
  6. SET QUOTED_IDENTIFIER ON
  7. GO
  8. --内层事务存储过程,演示如何处理才能在嵌套的事务存储过程中正确处理事务
  9. ALTER PROCEDURE [dbo].[innertran]
  10. @ID BIGINT ,
  11. @UserID BIGINT ,
  12. @Name VARCHAR(50)
  13. AS
  14. BEGIN
  15. DECLARE @TRANCOUNT int=(select @@TRANCOUNT)
  16. SET XACT_ABORT ON
  17. SET @TRANCOUNT=(select @@TRANCOUNT)
  18. PRINT '未进入子事务前全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))
  19. BEGIN TRAN tran1 --开始事务
  20. SAVE TRAN tranpoint --保存事务点
  21. SET @TRANCOUNT=(select @@TRANCOUNT)
  22. PRINT '进入子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))
  23. IF(EXISTS(SELECT TOP 1 * FROM dbo.test WHERE ID=@ID))
  24. BEGIN
  25. ROLLBACK TRAN tranpoint ; --回滚保存点的事务
  26. COMMIT TRAN tran1 ; --提示当前事务
  27. SET @TRANCOUNT=(select @@TRANCOUNT)
  28. PRINT '回滚子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))
  29. RETURN 0 ;
  30. END
  31. --业务逻辑开始
  32. INSERT dbo.test
  33. ( ID, UserID, Name)
  34. VALUES ( @ID,
  35. @UserID,
  36. @Name
  37. )
  38. --业务逻辑结束
  39. IF @@error <> 0
  40. BEGIN
  41. ROLLBACK TRAN tranpoint ; --回滚保存点的事务
  42. COMMIT TRAN tran1 ; --提示当前事务
  43. SET @TRANCOUNT=(select @@TRANCOUNT)
  44. PRINT '回滚子事务后全局@@TRANCOUNTT'+CAST(@TRANCOUNT AS VARCHAR(50))
  45. RETURN 0;
  46. END
  47. COMMIT TRAN tran1 ;
  48. SET XACT_ABORT OFF;
  49. SET @TRANCOUNT=(select @@TRANCOUNT)
  50. PRINT '提交子事务后全局@@TRANCOUNT'+CAST(@TRANCOUNT AS VARCHAR(50))
  51. RETURN 1 ;
  52. END

父过程如下: 

  1. USE [TestDB]
  2. GO
  3. /****** Object: StoredProcedure [dbo].[outertran] Script Date: 02/17/2013 16:27:13 ******/
  4. SET ANSI_NULLS ON
  5. GO
  6. SET QUOTED_IDENTIFIER ON
  7. GO
  8. -- =============================================
  9. -- Author: <Author,,Name>
  10. -- Create date: <Create Date,,>
  11. -- Description: <外层存储过程>
  12. -- =============================================
  13. ALTER PROCEDURE [dbo].[outertran]
  14. @ID BIGINT,
  15. @UserID BIGINT,
  16. @Name VARCHAR(50)
  17. AS
  18. DECLARE @TRANCOUNT int=(select @@TRANCOUNT)
  19. PRINT '未进入父事务前全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))
  20. BEGIN TRAN
  21. SET @TRANCOUNT=(select @@TRANCOUNT)
  22. PRINT '进入父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))
  23. DECLARE @result INT
  24.  
  25. EXEC @result = innertran @ID = @ID, @UserID = @UserID, @Name =@Name
  26.  
  27. IF ( @result <= 0 )
  28. BEGIN
  29. ROLLBACK TRAN ;
  30. SET @TRANCOUNT=(select @@TRANCOUNT)
  31. PRINT '回滚父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))
  32. RETURN ;
  33. END
  34. COMMIT TRAN
  35. SET @TRANCOUNT=(select @@TRANCOUNT)
  36. PRINT '提交父事务后全局@@TRANCOUNT:'+CAST(@TRANCOUNT AS VARCHAR(50))

调用父存储过程:

  1. USE [TestDB]
  2. GO
  3.  
  4. DECLARE @return_value int
  5.  
  6. EXEC @return_value = [dbo].[outertran]
  7. @ID = 0,
  8. @UserID = 0,
  9. @Name = N'0'
  10.  
  11. SELECT 'Return Value' = @return_value
  12.  
  13. GO

结果如下:

  1. 未进入父事务前全局@@TRANCOUNT:0
  2. 进入父事务后全局@@TRANCOUNT:1
  3. 未进入子事务前全局@@TRANCOUNT:1
  4. 进入子事务后全局@@TRANCOUNT:2
  5. 回滚子事务后全局@@TRANCOUNT:1
  6. 回滚父事务后全局@@TRANCOUNT:0

不会再报"EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配"之类的错误了,实际上就是在每个嵌套的子过程中标明当前事务点,每个子事务 只提交/回滚 子事务点,而不是回滚整个事务!

实际开发中还是会出现事务错乱的情况,如在try...catch...中

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号