目前JavaWeb系统的框架容器基本都是使用Spring管理的,其中事务管理也是比较重要的,之前也是看过,网上介绍的文章也很多,但毕竟不是自己的,也借此机会总结下Spring对事务的管理,希望对大家有用。
1.事务基础
1.1.事务的基本概念
数据库事务(Database Transaction):是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 “一荣俱荣,一损俱损”最能体现事务的的思想。也就是这些操作要么所有执行成功,所有执行失败。
数据库事务需要满足4个特性:原子性(Atomic),一致性(Consistency),隔离性(Isolation),持久性(Durabiliy),简称为ACID原则。
- 原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才能提交,若事务中的任务一个数据库操作失败,已经执行的任务操作都必须撤销,让数据库返回到初始状态。
- 一致性:事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。如在上超市买东西付款时,从你的账户扣钱,超市的账户价钱,但不管操作是否成功,你和超市的存款总额应是不变的。
- 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。说白了就是同时存在两个事务一起操作数据时,都会在各自的事务空间中,对数据的操作不会对对方产生干扰。但,各个数据库实现也没有做到完全无干扰,数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
- 持久性:一旦事务提交成功后,事务中所有的数据操作都必须持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
上面这些特性,数据的“一致性”是最终目标,其他特性都是为他服务。
1.2.事务并发带来的问题
同一时刻有可能有多个事务访问数据库的同一个资源,如果没有采取必要的隔离措施,就会导致各种并发问题,破坏数据的完整性。总共包括5类,3种数据读取问题(脏读、不可重复读和幻象读)以及2类数据更新问题(第一类丢失更新和第二类丢失更新),下面分别说下
- 脏读(dirty read)
事务A读取事务B尚未提交的更改数据,并在这个数据的基础上操作。如果这时正好事务B回滚了,那么事务A读取到数据就是脏数据,也即是脏读。
时间 | 转账事务A | 取款事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元把余额改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务余额恢复为1000元 | |
T7 | 汇入100元把余额改为600元 | |
T8 | 提交事务 |
脏读对Oracle中不会发生,因为Oracle数据库使用了数据库版本的机制,在回滚段为数据的每个变化都保存一个版本,使数据的更改不影响数据的读取。
- 不可重复度(unrepeatable read)
不可重复读是指事务A读取了事务B已经提交的更改数据。如A在取款事务的过程中,B往改账户转账100元,A两次读取的账户余额发生不一致。
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元和T4读取的不一致 |
- 幻象读(phantom read)
幻象读和不可重复度类似,即事务A读取事务B提交的新增数据,这时事务A将出现幻象读的问题。幻象读一般发生在计算统计数据的事务中,如:银行系统在同一个事务中,两次统计存款账户的总金额,在两次统计过程中,刚好新增了一个存款账户,并存入100元,这时,两次统计的总金额将不一致。
时间 | 统计金额事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款数为1000元 | |
T4 | 新增一个存款账户,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计存款数为1100元(幻象读) |
- 不可重复读和幻象读的区别
不可重复读在于记录的值,即读取到其他已经提交事务的更改数据。
幻象读在于记录的数量,即读取到其他已经提交事务的新增数据。
数据库的解决方法:
解决不可重复读——添加行级锁
解决幻象读——添加表级锁
- 第一类丢失更新
事务A撤销时,把已经提交的事务B的更新数据覆盖了。这种错误可能造成很严重的问题。看下面例子
时间 | 取款事务A | 转账事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元把余额改为1100元 | |
T6 | 提交事务 | |
T7 | 取出100元把余额改为900元 | |
T8 | 撤销事务 | |
余额恢复为1000元(丢失更新) |
- 第二类丢失更新
事务A覆盖事务B已经提交的数据,造成事务B所做操作丢失。
2.事务隔离级别
数据库为了解决1.2节中描述的事务并发问题通常是使用数据库锁机制,但数据库中的锁机制用户直接使用很麻烦,所以数据库提供了自动锁机制。即用户指定相应的事务隔离级别,数据库会添加合适的锁。
ANSI/ISO SQL 92标准定义了4个等级的事务隔离级别,不同事务隔离级别能够解决的数据并发问题的能力是不同的。如下:
隔离级别 | 脏读 | 不可重复读 | 幻象读 | 第一类丢失更新 | 第二类丢失更新 |
---|---|---|---|---|---|
READ UNCOMMITED | 允许 | 允许 | 允许 | 不允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 | 不允许 | 允许 |
REPEATABLE READ | 不允许 | 不允许 | 允许 | 不允许 | 不允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 | 不允许 | 不允许 |
事务的隔离级别和数据库的并发性是对立的,两者此增彼涨。使用READ UNCOMMITED隔离级别的数据库拥有最高的并发性和吞吐量,使用SERIALIZABLE隔离级别的数据库并发性和吞吐量最低。
想了解更多技术文章信息,请继续关注wiliam.s Blog,谢谢,欢迎来访!
参考资料
《Spring3.x企业应用开发实战》陈雄华 林开雄著