数据库事务:ACID + 读取/更新问题
Posted Java技术博文
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库事务:ACID + 读取/更新问题相关的知识,希望对你有一定的参考价值。
目录[阅读本文需要6分钟]:
数据库事务是什么?
1.1. 原子性
1.2. 一致性
1.3. 隔离性
1.4. 持久性
并发事务的读取问题
2.1. 脏读
2.2. 不可重复读
2.3. 幻读
2.4. 不可重复读和幻读的区别
并发事务的更新问题
3.1. 第一类丢失更新
3.2. 第二类丢失更新
1、数据库事务是什么?
数据库事务是个泾渭分明的概念。要么是黑,要么是白,中间没有灰色地带。一系列操作要么全部执行成功,要么全不执行,一荣俱荣一损俱损。
我们可以从总体上像上面那样把握这个概念,但具体到理论层面,需要满足以下四个特性,才称得上一个事务。
1.1 原子性
我们知道一个事务含有>=1条的数据库操作语句,而这些操作是一个原子操作,原子是不可再分的,也就是说,这些操作要么全部执行成功;要么由于其中的某条操作失败,而把之前的操作全部回滚,也就相当于没有执行,让数据库返回到了事务执行前的状态。不会出现只执行了事务中的部分数据库操作,而其他的没有执行这种情况。
1.2 一致性
事务操作完成后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。可以简单地理解为能量守恒。比如银行存了A客户100元,B客户100元,A客户和B客户总共在银行存了100(A)+ 100( B) = 200 元。如果A客户向B客户转了50元,那么A客户在银行的存款变为100(A) - 50 = 50元;B客户在银行的存款变为100(B) + 50 = 150元。总金额还是50(A) + 150(B) = 200元。这个总金额200就是业务规则,事务操作完成后,业务规则保持一致。
1.3 隔离性
隔离性是相对于并发执行的多个事务而言的。多个事务并发操作,相互之间不会对对方产生干扰。要做到完全没干扰,只能串行地执行各个并发的事务,效率会很低,实际应用中,使用完全没干扰的场景很少。所以数据库会给出了事务的隔离级别,客户可以根据实际需要灵活地选择。
1.4 持久性
一旦事务提交成功以后,事务中的所有数据库操作都应该被持久化到数据库中。即使在事务刚提交成功,数据库就崩溃了,在数据库重启之后,也能通过某种机制恢复数据。
像开闭原则是所有原则的根本一样,一致性是事务的最终目标,其他的都是措施、要求。上面提到了数据库能够通过某种机制恢复数据,这种机制的原理大致如下:我们对数据库做操作时,数据库管理系统里有个重执行日志,这个日志记录了每一个数据库变化对应的操作。如果在执行数据库事务的过程中,某条语句发生了错误,数据库可以根据重执行日志,回滚之前的操作;如果事务提交后,但还没有完成持久化,数据库崩溃了,在重启时,数据库也可以根据重执行日志,执行对尚未持久化的操作。
2. 并发事务的读取问题
一个事务对数据进行读操作,而另一个事务对数据进行修改或增加操作,会出现哪些问题?
2.1 脏读
A事务读取了B事务尚未提交的更改数据,并基于读取到的数据做了操作,如果B事务回滚了操作,那么A事务读取到的数据就不会被承认,也就是脏读。
时间 |
转账事务A |
取款事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 取出500元把余额改为500元 | |
T5 | 查询账户余额为500元(脏读) | |
T6 | 撤销事务余额恢复为1000元 | |
T7 | 汇入100元把余额改为600元 | |
T8 | 提交事务 |
在上面的例子中,事务A在T5时刻,读取到了事务B更改后但尚未提交的数据500,并基于500做操作。之后事务B撤销了操作,但事务A已经感知不到这个操作,在T7时刻还是基于500做操作,导致最后损失500元。
2.2 不可重复读
现象是A事务读取B事务的同一条数据,并且读取次数>=2,而每次读取的结果都不一样。产生原因是A事务已经读取了一次数据,之后又读取了B事务已经提交的更改数据。
时间 |
取款事务A |
转账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取出100元把余额改为900元 | |
T6 | 提交事务 | |
T7 | 查询账户余额为900元(和T4读取的不一致) |
上例中,事务A在T4时刻读取了账户余额,在T5时刻事务B更改了余额,并在T6时刻提交了事务,在T7时刻事务A再次读取余额,发现和T4时刻的余额数据不一致。
2.3 幻读
A事务读取了B事务提交的新增数据,导致幻读。
时间 |
统计金额事务A |
转账事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 统计总存款数为10000元 | |
T4 | 新增一个存款账户,存款为100元 | |
T5 | 提交事务 | |
T6 | 再次统计总存款数为10100元(幻象读) |
事务A在T3时刻读取了存款总数,在T4时刻,事务B新增了一个存款账户,并在T5时刻提交了事务,那么在T6时刻,事务A读取存款总额时就会出现幻读。
2.4 不可重复读和幻读的区别
不可重复读,强调的是A事务读取了B事务已经提交的更改数据,重点在update和delete,注意是更改数据,不是新增的数据,而幻读强调的是A事务读取了B事务已经提交的新增数据,重点在insert。避免不可重复读,加行级锁即可,避免幻读则需要加表级锁。
3. 并发事务的更新问题
上面讲了一个事务在读数据,另一个事务在修改或增加数据会出现的问题,那两个事务都在更新数据会出现什么问题呢?
3.1. 第一类丢失更新
回滚丢失(lostupdate):A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题。标准定义的所有隔离界别都不允许第一类丢失更新发生。
时间 |
取款事务A |
转账事务B |
T1 |
开始事务 |
|
T2 |
开始事务 |
|
T3 |
查询账户余额为1000元 |
|
T4 |
查询账户余额为1000元 |
|
T5 |
汇入100元把余额改为1100元 |
|
T6 |
提交事务 |
|
T7 |
取出100元把余额改为900元 |
|
T8 |
撤销事务 |
|
T9 |
余额恢复为1000 元(丢失更新) |
3.2. 第二类丢失更新
覆盖丢失(两次更新问题):A事务覆盖B事务已经提交的数据,导致B事务所做的操作丢失。
时间 |
转账事务A |
取款事务B |
T1 |
开始事务 |
|
T2 |
开始事务 |
|
T3 |
查询账户余额为1000元 |
|
T4 |
查询账户余额为1000元 |
|
T5 |
取出100元把余额改为900元 |
|
T6 |
提交事务 |
|
T7 |
汇入100元 |
|
T8 |
提交事务 |
|
T9 |
把余额改为1100 元(丢失更新) |
以上是关于数据库事务:ACID + 读取/更新问题的主要内容,如果未能解决你的问题,请参考以下文章