数据库事务:ACID + 读取/更新问题

Posted Java技术博文

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据库事务:ACID + 读取/更新问题相关的知识,希望对你有一定的参考价值。

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 数据库事务:ACID + 读取/更新问题 数据库事务:ACID + 读取/更新问题

目录[阅读本文需要6分钟]:

  1. 数据库事务是什么?

    1.1. 原子性

    1.2. 一致性

    1.3. 隔离性

    1.4. 持久性

  2. 并发事务的读取问题

    2.1. 脏读

    2.2. 不可重复读

    2.3. 幻读

    2.4. 不可重复读和幻读的区别

  3. 并发事务的更新问题

    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 + 读取/更新问题的主要内容,如果未能解决你的问题,请参考以下文章

MySQL-事务

MySQL事务

事务的ACID以及事务的隔离级别

InnoDB事务不能只懂ACID

InnoDB事务不能只懂ACID

InnoDB事务不能只懂ACID