一文读懂数据库事务

2020-05-28 00:00:00 数据 事务 隔离 账户 块钱


什么是事务

根据维基百科的定义,一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:1)为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法;2)当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。简单来讲,事务的作用至少有两个:保证数据一致性,以及对数据进行隔离。这么说大家可能还不太好理解,我们还是举个例子说明一下吧。

我们以A向B转账100块钱为例,在一笔转账操作中,至少必须包含2个操作:

  1. 将A的账户余额扣除100块钱(假设转账前A的账户余额大于100块钱)

  2. 将B的账户余额增加100块钱

如果在转账的过程中不使用事务,那么有可能会出现在将A的账户扣除100块钱之后,发生了不可预知的异常(比方说服务器宕机了),导致没来得及将B的账户增加100块钱,如此一来,数据库就产生了数据不一致的情况,即A的账户扣了100块钱,但是B的账户并没有相应地增加100块钱,也就是说有100块钱“不翼而飞”了。而如果将这2个操作放在一个事务里执行的话,由于事务中的操作要么全部执行,要么全部不执行,所以可以保证数据一致性。这就是在数据一致性要求比较高的场景下使用事务的好处。

事务的特性

事务的特性主要有4个:原子性,一致性,隔离性与持久性,也叫做ACID。

  1. 原子性,指的是一个事务必须被视为一个不可分割的小工作单元,整个事务中的操作要么全部提交成功,要么全部失败回滚,也就是说不可能只执行事务中的部分操作,这就是事务的原子性。

  2. 一致性,指的是事务应确保数据库的状态从一个一致状态转变为另一个一致状态。以上述转账的例子为例,转账后A的账户减少100块钱,B的账户增加100块钱是满足数据一致性的,事务可以保证事务成功提交之后数据库转化为这个状态,如果事务失败回滚了,则还是保持初的一致性状态,而不会出现上述的A的账户减少100块钱,B的账户没有变化的不一致的中间状态。

  3. 隔离性,指的是多个事务并发执行时,一个事务的执行不应影响其他事务的执行。比如当A向B转账的过程中,B也可以向A转账,这2个事务互不影响,而且通常来讲也是互不可见的(注意,是通常来讲“不可见”,后续讲到事务隔离级别的时候再详述)。

  4. 持久性,指的是已被提交的事务对数据库的修改应该被保存在数据库中。这个比较好理解,就是说已成功提交的事务会被地保存下来。

事务的隔离级别

前文说到,事务具有隔离性,而且通常来讲,不同事务之间是不可见的,然而现实中的事务隔离性并不都是不可见的,实际上,SQL标准定义了4种事务隔离级别:读未提交,读已提交,可重复读,串行化,下文将逐一讲解。

  1. 读未提交,指的是事务中未提交的修改,对于其他事务而言是可见的,这是隔离性低的一种级别了。在这种隔离级别下,会出现“脏读”的情况。所谓“脏读”指的是读取到了其他事务未提交的数据。我们以2个事务同时进行为例,因为事务A可以读到事务B的未提交的修改数据,假如说事务B在事务A结束之前因为发生异常而回滚了,那么A读到的事务B的未提交的数据就是“过期”的,如果事务A在这个“过期”数据上进行操作,那势必会造成数据不一致的情况。因此这种隔离级别在实际中很少使用。

  2. 读已提交,指的是一个事务开始时,只能看到其他已经提交的事务对数据所做的修改。这种隔离级别可以防止“脏读”的问题出现。但是这种情况可能会出现“不可重复读”的问题。所谓不可重复读是指,事务A先后2次读到的数据不一致。举个例子,事务A先读到小明的账户余额为100块钱,然后再做了其他操作(假设这些操作没有改变小明的余额),这个时候事务B将小明的账户扣了10块钱,变成90块钱了并提交了,由于事务A可以读到事务B提交的修改数据,所以当事务A执行了其他操作后再读取小明的余额时就变成90块了,因此前后2次读取的数据不一致,故出现了“不可重复读”的问题。

  3. 可重复读,指的是可以避免上述“不可重复读”的情况出现,即它可以保证在同个事务中,先后读到的同一行数据是一致的,然而这种隔离级别下会出现另一个问题,就是“幻象读”。“幻象读”指的是事务A读取到了事务B新增的数据,因此出现了“幻行”。举个例子,事务A一开始查询到小明在1月份一共网购了100次,然后再进行其他操作(假设这些操作没有改变小明的网购记录数),然后事务B插入了一条小明新的网购记录并提交了,接下来事务A再统计出小明的网购记录,变成101次了,出现了“幻行”,也就是出现了“幻象读”。

  4. 串性化,指的是强制事务串行执行,其可以避免“幻象读”的问题出现,这是严格的隔离级别了。因为串行化需要在发生竞争的数据上加锁,所以并发性能不高,只有在对数据一致性要求非常高且并发度不高的情况下才会考虑使用这种隔离级别。


往期推荐:

使用 RestTemplate 进行第三方Rest服务调用


原创视频 | 理解 Java 中的 Lambda 表达式


相关文章