前言
本专题大纲:
我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可以直接跳过本文,等待接下来两篇应用及源码分析,不过我觉得知识的学习应该慢慢行成一个体系,为了建立一个完善的体系应该要对数据库本身事务的实现有一定认知才行。
本文为Spring事务专题第三篇,在前两篇文章中我们已经对Spring中的数据访问有了一定的了解,那么从本文开始我们正式接触事务,在分析Spring中事务的实现之前我们应该要对事务本身有一定的了解,同时也要对数据库层面的事务如何实现有一定了解。话不多说,我们开始正文
本文大纲:
初识事务
为什么需要事务?
这里又要掏出那个烂大街的银行转账案例了,以A、B两个账户的转账为例,假设现在要从A账户向B账户中转入1000员,当进行转账时,需要先从银行账户A中取出钱,然后再存入银行账户B中,SQL样本如下:
// 第一步:A账户余额减少减少1000 update balance set money = money -500 where name= ‘A’; // 第二步:B账户余额增加1000 update balance set money = money +500 where name= ‘B’;
如果在完成了第1步的时候突然宕机了,A的钱减少了而B的钱没有增加,那A岂不是白白丢了1000元,这时候就需要用到我们的事务了,开启事务后SQL样本如下:
// 第一步:开始事务 start transaction; // 第二步:A账户余额减少减少1000 update balance set money = money -500 where name= ‘A’; // 第三步:B账户余额增加1000 update balance set money = money +500 where name= ‘B’; // 第四步:提交事务 commit;
什么是事务
事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含一个或多个sql语句,这些语句要么都执行成功,要么全部执行失败。
事务的四大特性(ACID)
原子性(Atomicity,或称不可分割性)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中所有的操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性
一致性(Consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态,在事务开始之前和之后,数据库的完整性约束没有被破坏。在前面的例子中,事务结束前后A、B账户总额始终保持不变
隔离性(Isolation)
隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable (可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。
持久性(Durability)
持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的隔离级别
在前文中我们介绍了隔离性,但实际上隔离性比想象的要复杂的多。在SQL标准中定义了四种隔离级别,每一种隔离级别都规定了一个事务所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的,较低级别的隔离通常可以执行跟高的并发,系统的开销也更低
未提交读(READ UNCOMMITTED)
在这个隔离级别下,事务的修改即使没有提交,对其他事务也是可见的。事务可以读取未提交的数据,这也被称之为脏读。这个级别会带来很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但是却会带来很多问题,除非真的有非常必要的理由,在实际应用中一般很少使用。
提交读(REDA COMMITED)
大多数数据系统的默认隔离级别都是REDA COMMITED(MySql不是),REDA COMMITED满足前面提到的隔离性的简单定义:一个事务开始时,只能看到已经提交的事务所做的修改。换句话说,一个事物从开始直到提交前,所做的修改对其他事务不可见。这个级别有时候也叫做不可重复读,因为执行两次相同的查询可能会得到不同的结果。
可重复读(REPEATABLE READ)
REPEATABLE READ解决了脏读以及不可重复度的问题。该级别保证了同一个事务多次读取同样记录的结果是一致的。但是理论上,可重复度还是无法解决另外一个幻读的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,就会产生幻行。
不可重复读跟幻读的区别在于,前者是数据发生了变化,后者是数据的行数发生了变化。
可串行化(SERIALIZABLE)
SERIALIZABLE是最高的隔离级别,它通过强制事务串行执行,避免前面说的幻读。简单来说SERIALIZABLE会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁争用的问题。实际应用中也很少使用这个隔离级别,只有在非常需要确保数据一致性而且可以接受没有并发的情况下,才考虑此级别。
保存点
我们可以在事务执行的过程中定义保存点,在回滚时直接指定回滚到指定的保存点而不是事务开始之初,有点像我们玩游戏的时候可以存档而不是每次都要重新再来
定义保存点的语法如下:
SAVEPOINT 保存点名称;
当我们想回滚到某个保存点时,可以使用下边这个语句(下边语句中的单词WORK和SAVEPOINT是可有可无的):
ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;