SQL Server 2008中SQL应用系列--目录索引

SQL事务
 
一、事务概念
    事务是一种机制、是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行。因此事务是一个不可分割的工作逻辑单元。在数据库系统上执行并发操作时事务是作为最小的控制单元来使用的。这特别适用于多用户同时操作的数据通信系统。例如:订票、银行、保险公司以及证券交易系统等。
 
二、事务属性
事务4大属性:
1   原子性(Atomicity):事务是一个完整的操作。
2   一致性(Consistency):当事务完成时,数据必须处于一致状态。
3   隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的。
4   持久性(Durability):事务完成后,它对于系统的影响是永久性的。
 
三、创建事务
T-SQL中管理事务的语句:
1 开始事务: begin transaction
2 提交事务:commit transaction
3 回滚事务: rollback transaction
 
事务分类:
1 显式事务:用begin transaction明确指定事务的开始。
2 隐性事务:打开隐性事务:set implicit_transactions on,当以隐性事务模式操作时,SQL Servler将在提交或回滚事务后自动启动新事务。无法描述事务的开始,只需要提交或回滚事务。
3 自动提交事务:SQL Server的默认模式,它将每条单独的T-SQL语句视为一个事务。如果成功执行,则自动提交,否则回滚。

  在SQL Server 2000中,我们一般使用RaiseError(http://msdn.microsoft.com/zh-cn/library/ms177497.aspx)来抛出错误交给应用程序来处理。看MSDN示例(http://msdn.microsoft.com/zh-cn/library/aa238452%28v=sql.80%29.aspx),自从SQL Server 2005集成Try…Catch功能以后,我们使用时更加灵活,到了SQL Server 2012,更推出了强大的THROW,处理错误显得更为精简。本文对此作一个小小的展示。

  首先,我们假定两个基本表如下:

  1. --创建两个测试表
  2. IF NOT OBJECT_ID('Score') IS NULL
  3. DROP TABLE [Score]
  4. GO
  5. IF NOT OBJECT_ID('Student') IS NULL
  6. DROP TABLE [Student]
  7. GO
  8. CREATE TABLE Student
  9. (stuid int NOT NULL PRIMARY KEY,
  10. stuName Nvarchar(20)
  11. )
  12. CREATE TABLE Score
  13. (stuid int NOT NULL REFERENCES Student(stuid),--外键
  14. scoreValue int
  15. )
  16. GO
  17. INSERT INTO Student VALUES (101,'胡一刀')
  18. INSERT INTO Student VALUES (102,'袁承志')
  19. INSERT INTO Student VALUES (103,'陈家洛')
  20. INSERT INTO student VALUES (104,'张三丰')
  21. GO
  22. SELECT * FROM Student
  23. /*
  24. stuid stuName
  25. 101 胡一刀
  26. 102 袁承志
  27. 103 陈家洛
  28. 104 张三丰
  29. */

  我们从一个最简单的例子入手:

例一:

  1. /********* 调用运行时错误 ***************/
  2. /********* 3w@live.cn 邀月***************/
  3. SET XACT_ABORT OFF
  4. BEGIN TRAN
  5. INSERT INTO Score VALUES (101,80)
  6. INSERT INTO Score VALUES (102,87)
  7. INSERT INTO Score VALUES (107, 59) /* 外键错误 */
  8. -----SELECT 1/0 /* 除数为0错误 */
  9. INSERT INTO Score VALUES (103,100)
  10. INSERT INTO Score VALUES (104,99)
  11. COMMIT TRAN
  12. GO

  先不看结果,我想问一下,该语句执行完毕后,Score表会插入几条记录?估计可能有人说是2条,有人说0条,也可能有人说4条。

  实际上,我希望是0条,但结果是4条! 

  1. /*
  2. (1 row(s) affected)
  3. (1 row(s) affected)
  4. Msg 547, Level 16, State 0, Line 5
  5. The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Score__stuid__01D345B0". The conflict occurred in database "testDb2", table "dbo.Student", column 'stuid'.
  6. The statement has been terminated.
  7. (1 row(s) affected)
  8. (1 row(s) affected)
  9. */
  10. SELECT * from Score
  11. /*
  12. stuid scoreValue
  13. 101 80
  14. 102 87
  15. 103 100
  16. 104 99
  17. */

  我对这个结果也有点惊讶,我希望它出错回滚,于是修改:

例二:

  1. /********* 调用运行时错误 ***************/
  2. /********* 3w@live.cn 邀月***************/
  3. TRUNCATE table Score
  4. GO
  5. SET XACT_ABORT OFF
  6. BEGIN TRAN
  7. INSERT INTO Score VALUES (101,80)
  8. INSERT INTO Score VALUES (102,87)
  9. INSERT INTO Score VALUES (107, 59) /* 外键错误 */
  10. ----SELECT 1/0
  11. --INSERT INTO Score VALUES (103,100)
  12. --INSERT INTO Score VALUES (104,99)
  13. PRINT '@@ERROR是:'+cast(@@ERROR as nvarchar(10))
  14. IF @@ERROR<>0
  15. ROLLBACK TRAN
  16. ELSE
  17. COMMIT TRAN
  18. GO

  我先提示一下大家,这个语句中的@@ERROR值是547,那么此时,Score表中有几条记录?

  答案是2条!

  可能有人开始摇头了,那么问题的关键在哪儿呢?对,就是这个“XACT_ABORT ”开关,查MSDN(http://msdn.microsoft.com/zh-cn/library/ms188792.aspx),

官方解释:它用于指定当 Transact-SQL 语句出现运行时错误时,SQL Server 是否自动回滚到当前事务。当 SET XACT_ABORT 为 ON 时,如果执行 Transact-SQL 语句产生运行时错误,则整个事务将终止并回滚。当 SET XACT_ABORT 为 OFF 时,有时只回滚产生错误的 Transact-SQL 语句,而事务将继续进行处理。 如果错误很严重,那么即使 SET XACT_ABORT 为 OFF,也可能回滚整个事务。 OFF 是默认设置。编译错误(如语法错误)不受 SET XACT_ABORT 的影响。对于大多数 OLE DB 访问接口(包括 SQL Server),必须将隐式或显示事务中的数据修改语句中的 XACT_ABORT 设置为 ON。 唯一不需要该选项的情况是在提供程序支持嵌套事务时。

  这里,红色的一句话是关键,那么“有时”究竟是指什么时候呢?查资料知:(http://msdn.microsoft.com/zh-cn/library/ms164086.aspx

  大致分为以下四个级别:

    当等级SEVERITY为0-10时,为“信息性消息”,最轻。

    当等级为11-16时,为“用户可以纠正的数据库引擎错误”。如除数为零,等级为16

    当等级为17-19时,为“需要DBA注意的错误”。如内存不足、数据库引擎已到极限等。

    当等级为20-25时,为“致命错误或系统问题”。如硬件或软件损坏、完整性问题、媒体故障等。

  用户也可以自定义错误级别和类型。

  根据以上解释,我们最保险的方式是:Set XACT_ABORT ON

  当然,使用Try…Catch在Set XACT_ABORT OFF时也能按照我们的意愿回滚。

例三:

  1. /********* 使用Try Catch 构造一个错误记录 ***************/
  2. /********* 3w@live.cn 邀月 ***************/
  3. SET XACT_ABORT OFF
  4. BEGIN TRY
  5. BEGIN TRAN
  6. INSERT INTO Score VALUES (101,80)
  7. INSERT INTO Score VALUES (102,87)
  8. INSERT INTO Score VALUES (107, 59) /* 外键错误 */
  9. INSERT INTO Score VALUES (103,100)
  10. INSERT INTO Score VALUES (104,99)
  11. COMMIT TRAN
  12. PRINT '事务提交'
  13. END TRY
  14. BEGIN CATCH
  15. ROLLBACK
  16. PRINT '事务回滚' --构造一个错误信息记录
  17. SELECT ERROR_NUMBER() AS 错误号,
  18. ERROR_SEVERITY() AS 错误等级,
  19. ERROR_STATE() as 错误状态,
  20. DB_ID() as 数据库ID,
  21. DB_NAME() as 数据库名称,
  22. ERROR_MESSAGE() as 错误信息;
  23. END CATCH
  24. GO

  这个返回结果比较另类,它其实是一条拼凑起来的记录。

  记录并没有新增,因为Catch到错误而事务回滚了。

  使用RaiseError也可以把出错的信息抛给应用程序来处理。

例四:

  1. /********* 使用RaiseError 提交一个错误信息***************/
  2. /********* 3w@live.cn 邀月 ***************/
  3. SET XACT_ABORT OFF
  4. BEGIN TRY
  5. BEGIN TRAN
  6. INSERT INTO Score VALUES (101,80)
  7. INSERT INTO Score VALUES (102,87)
  8. INSERT INTO Score VALUES (107, 59) /* 外键错误 */
  9. INSERT INTO Score VALUES (103,100)
  10. INSERT INTO Score VALUES (104,99)
  11. COMMIT TRAN
  12. PRINT '事务提交'
  13. END TRY
  14. BEGIN CATCH
  15. ROLLBACK
  16. PRINT '事务回滚';--构造一个错误信息记录
  17. DECLARE @ErrorMessage NVARCHAR(4000);
  18. DECLARE @ErrorSeverity INT;
  19. DECLARE @ErrorState INT;
  20. SELECT @ErrorMessage = ERROR_MESSAGE(),
  21. @ErrorSeverity = ERROR_SEVERITY(),
  22. @ErrorState = ERROR_STATE();
  23. RAISERROR (@ErrorMessage, -- Message text.
  24. @ErrorSeverity, -- Severity.
  25. @ErrorState -- State.
  26. );
  27. END CATCH
  28. GO

  或者直接使用Throw也能达到RaiseError同样的效果,而且这是微软推崇的方式:其官方解释为“THROW 语句支持 SET XACT_ABORT,但 RAISERROR 不支持。 新应用程序应该改用 THROW,而不使用 RAISERROR。”其实,可能是微软在忽悠,因为,其实RaiseError也支持Set XACT_ABORT。

例五:

  1. /********* SQL 2012新增的Throw ***************/
  2. /********* 3w@live.cn 邀月***************/
  3. SET XACT_ABORT OFF
  4. BEGIN TRY
  5. BEGIN TRAN
  6. INSERT INTO score VALUES (101,80)
  7. INSERT INTO score VALUES (102,87)
  8. INSERT INTO score VALUES (107, 59) /* 外键错误 */
  9. INSERT INTO score VALUES (103,100)
  10. INSERT INTO score VALUES (104,99)
  11. COMMIT TRAN
  12. PRINT '事务提交'
  13. END TRY
  14. BEGIN CATCH
  15. ROLLBACK;
  16. PRINT '事务回滚';
  17. Throw;
  18. END CATCH
  19. GO

  不过,说实话,Throw好像很简练。

  说到这里,我有一个疑问:例四和例五的查询结果相同:

  1. /*
  2. (1 row(s) affected)
  3. (1 row(s) affected)
  4. (0 row(s) affected)
  5. 事务回滚
  6. Msg 547, Level 16, State 0, Line 13
  7. The INSERT statement conflicted with the FOREIGN KEY constraint "FK__Score__stuid__18B6AB08". The conflict occurred in database "testDb2", table "dbo.Student", column 'stuid'.
  8. */

  虽然因为回滚而没有插入数据,但是两个“(1 row(s) affected) ”还是让我吃了一惊,哪位高手能告诉我一下,这影响的两行SQL Server究竟是怎么处理的?先谢过了。

  既然,错误已经被捕获,那么有两种处理方式,一是直接在数据库中记录到表中。比如:我们可以建立一个数据库DBErrorLogs,

  1. /********* 生成错误日志记录表 ******/
  2. /********* 3w@live.cn 邀月***************/
  3. CREATE database DBErrorLogs
  4. GO
  5. USE DBErrorLogs
  6. GO
  7. CREATE TABLE [dbo].[ErrorLog](
  8. [nId] [bigint] IDENTITY(101,1) NOT NULL PRIMARY KEY,
  9. [dtDate] [datetime] NOT NULL,
  10. [sThread] [varchar](100) NOT NULL,
  11. [sLevel] [varchar](200) NOT NULL,
  12. [sLogger] [varchar](500) NOT NULL,
  13. [sMessage] [varchar](3000) NOT NULL,
  14. [sException] [varchar](4000) NULL
  15. )
  16. GO
  17. ALTER TABLE [dbo].[ErrorLog] ADD DEFAULT (getdate()) FOR [dtDate]
  18. GO

  在出错时直接插入相应信息到该表中即可。另外一种思路是交给应用程序来处理,比如下例中,我们用C#捕获错误,并用log4net记录回数据库中。C#中有相应的SQLException类,封装了相应的Error的等级、编号、出错信息等,真心方便。

  1. using System;
  2. using System.Text;
  3. using System.Data.SqlClient;
  4. using System.Data;
  5. namespace RaiseErrorDemo_Csharp
  6. {
  7. public class Program
  8. {
  9. #region Define Members
  10. private static log4net.ILog myLogger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  11. static string conn = "Data Source=AP4\\Net2012;Initial Catalog=Testdb2;Integrated Security=True";
  12. static string sql_RaiseError = @"
  13. /********* 使用RaiseError 提交一个错误信息***************/
  14. /********* 3w@live.cn 邀月 ***************/
  15. SET XACT_ABORT OFF
  16. BEGIN TRY
  17. BEGIN TRAN
  18. INSERT INTO Score VALUES (101,80)
  19. INSERT INTO Score VALUES (102,87)
  20. INSERT INTO Score VALUES (107, 59) /* 外键错误 */
  21. INSERT INTO Score VALUES (103,100)
  22. INSERT INTO Score VALUES (104,99)
  23. COMMIT TRAN
  24. PRINT '事务提交'
  25. END TRY
  26. BEGIN CATCH
  27. ROLLBACK
  28. PRINT '事务回滚';--构造一个错误信息记录
  29. DECLARE @ErrorMessage NVARCHAR(4000);
  30. DECLARE @ErrorSeverity INT;
  31. DECLARE @ErrorState INT;
  32. SELECT @ErrorMessage = ERROR_MESSAGE(),
  33. @ErrorSeverity = ERROR_SEVERITY(),
  34. @ErrorState = ERROR_STATE();
  35. RAISERROR (@ErrorMessage, -- Message text.
  36. @ErrorSeverity, -- Severity.
  37. @ErrorState -- State.
  38. );
  39. END CATCH
  40. ";
  41. static string sql_Throw = @"
  42. SET XACT_ABORT OFF
  43. BEGIN TRY
  44. BEGIN TRAN
  45. INSERT INTO score VALUES (101,80)
  46. INSERT INTO score VALUES (102,87)
  47. INSERT INTO score VALUES (107, 59) /* 外键错误 */
  48. INSERT INTO score VALUES (103,100)
  49. INSERT INTO score VALUES (104,99)
  50. COMMIT TRAN
  51. PRINT '事务提交'
  52. END TRY
  53. BEGIN CATCH
  54. ROLLBACK;
  55. PRINT '事务回滚';
  56. Throw;
  57. END CATCH
  58. ";
  59. #endregion
  60. #region Methods
  61. /// <summary>
  62. /// 主函数
  63. /// </summary>
  64. /// <param name="args"></param>
  65. static void Main(string[] args)
  66. {
  67. CatchSQLError(sql_RaiseError);
  68. Console.WriteLine("-----------------------------------------------");
  69. CatchSQLError(sql_Throw);
  70. Console.ReadKey();
  71. }
  72. /// <summary>
  73. /// 捕获错误信息
  74. /// </summary>
  75. /// <param name="strSQL"></param>
  76. public static void CatchSQLError(string strSQL)
  77. {
  78. string connectionString = conn;
  79. SqlConnection connection = new SqlConnection(connectionString);
  80. SqlCommand cmd2 = new SqlCommand(strSQL, connection);
  81. cmd2.CommandType = CommandType.Text;
  82. try
  83. {
  84. connection.Open();
  85. cmd2.ExecuteNonQuery();
  86. }
  87. catch (SqlException err)
  88. {
  89. string strErr = GetPreError(err.Class);
  90. //显示出错信息
  91. Console.WriteLine("错误等级:" + err.Class + Environment.NewLine + strErr + err.Message);
  92. //记录错误到数据库中
  93. myLogger.Error(strErr, err);
  94. }
  95. finally
  96. {
  97. connection.Close();
  98. }
  99. }
  100. /// <summary>
  101. /// 辅助函数
  102. /// </summary>
  103. /// <param name="b"></param>
  104. /// <returns></returns>
  105. public static string GetPreError(byte b)
  106. {
  107. string strErr = string.Empty;
  108. if (b >= 0 && b <= 10)
  109. {
  110. strErr = "信息性信息:";
  111. }
  112. else if (b >= 11 && b <= 16)
  113. {
  114. strErr = "用户可以纠正的数据库引擎错误:";
  115. }
  116. else if (b >= 17 && b <= 19)
  117. {
  118. strErr = "需要DBA注意的错误:";
  119. }
  120. else if (b >= 20 && b <= 25)
  121. {
  122. strErr = "致命错误或系统问题:";
  123. }
  124. else
  125. {
  126. strErr = "地球要毁灭了,快跑啊:";
  127. }
  128. return strErr;
  129. }
  130. #endregion
  131. }
  132. }

文后附有C#源码。执行效果:

小结:

1、SQL Server处理错误时有一个重要的开关XACT_ABORT,没事的时候,记得把它打开。

2、SQL Server提供的错误信息很丰富,请区分等级采取相应的对策,当然,还可以自己增加更为实用贴切的自定义错误类型。

下载源码

SqlServer事务回滚(2)的更多相关文章

  1. SqlServer 事务回滚(1)

    SQL事务   一.事务概念     事务是一种机制.是一种操作序列,它包含了一组数据库操作命令,这组命令要么全部执行,要么全部不执行.因此事务是一个不可分割的工作逻辑单元.在数据库系统上执行并发操作 ...

  2. SQL 事务回滚

    事务(Transaction)是并发控制的单位,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务,SQL Server能将逻辑相关的一组操作绑定在一起,以便服 ...

  3. Spring异常抛出触发事务回滚

    Spring.EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚 /** * 如果在spring事务配置中不为切入点(如这里的切入点可以定义成test*) ...

  4. 【Java EE 学习 19】【使用过滤器实现全站压缩】【使用ThreadLocal模式解决跨DAO事务回滚问题】

    一.使用过滤器实现全站压缩 1.目标:对网站的所有JSP页面进行页面压缩,减少用户流量的使用.但是对图片和视频不进行压缩,因为图片和视频的压缩率很小,而且处理所需要的服务器资源很大. 2.实现原理: ...

  5. jfinal 基本应用 --事务回滚

    事务回滚 1.当时需要用到事务回滚,但是看到网上只有问题,没有真实的到底怎么用法. 2.我看了一下文档,结合了网上的大神的博客. 第一种方法: Db.tx(new IAtom(){ @Override ...

  6. Spring @Transactional ——事务回滚

    工作原理运行配置@Transactional注解的测试类的时候,具体会发生如下步骤1)事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与Dat ...

  7. MySQL(22):事务管理之 事务回滚

    1. 在操作事务的时候,如果发现当前事务操作是不合理的,此时只要还没有提交事务,就可以通过回滚取消当前事务,接下来就针对事务的回滚进行详细讲解. 2. 为了演示回滚操作,在上一个笔记案例基础之上,此时 ...

  8. mysql数据库 索引 事务和事务回滚

    mysql索引 索引相当于书的目录优点:加快数据的查询速度缺点:占物理存储空间,添加,删除,会减慢写的速度 查看表使用的索引 mysql> show index from 表名\G;(\G分行显 ...

  9. (转)spring异常抛出触发事务回滚策略

    背景:在面试时候问到事务方法在调用过程中出现异常,是否会传递的问题,平时接触的比较少,有些懵逼. spring异常抛出触发事务回滚策略 Spring.EJB的声明式事务默认情况下都是在抛出unchec ...

随机推荐

  1. iOS 动态计算文本内容的高度

    关于ios 下动态计算文本内容的高度,经过查阅和网上搜素,现在看到的有以下几种方法: 1. //  获取字符串的大小  ios6 - (CGSize)getStringRect_:(NSString* ...

  2. js递归

    先从外层往里调,再反. 要想明白,必须明白执行过程. 如果再不理解,就看函数功能.   函数里自己调自己就是递归!

  3. (转)Ehcache 整合Spring 使用页面、对象缓存

    Ehcache在很多项目中都出现过,用法也比较简单.一般的加些配置就可以了,而且Ehcache可以对页面.对象.数据进行缓存,同时支持集群/分布式缓存.如果整合Spring.Hibernate也非常的 ...

  4. setw()函数

  5. jquery学习笔记---闭包,原型链,this关键字

    网上的资料很多,关于闭包,原型链,面向对象之内的.本人也有一点自己的总结. 关于this: this 的值取决于 function 被调用的方式,一共有四种, 如果一个 function 是一个对象的 ...

  6. javascript - 浏览器对象

    Navigator对象 弹出窗口 Cookies Browser Objects 参考手册 参考手册描述了每个对象的属性和方法,并提供了在线实例. Window 对象 Navigator 对象 Scr ...

  7. Fallout4 Creation Kit

    按住SHIFT是旋转视角,按住鼠标中键 E是移动物品 双击W是旋转物品 数字键2 是调整物品大小

  8. HDU5558 Alice's Classified Message(合肥区域赛 后缀数组)

    当初合肥区域赛的题(现场赛改了数据范围就暴力过了),可惜当初后缀数组算法的名字都没听过,现在重做下. i从1到n - 1,每次枚举rank[i]附近的排名,并记录当起点小于i时的LCP(rank[i] ...

  9. oracle相关环境变量配置

    ORACLE_HOME:D:\Program File\oracle\product\10.2.0\db_1 ORACLE_SID:orcl Path中增加:D:\ProgramFile\oracle ...

  10. 堆栈C实现

    标准C语言没有像C++那样可以直接调用的STL容器,所以在c语言中实现容器功能就得自己去定义堆栈结构: stack.h /************this head file defines a st ...