关于事务一致性与原子性的思考

零、事务

事务是一系列动作的合集,拥有ACID四种特性,分别是原子性、一致性、隔离性和持久性,先来简单回顾一下几个特性的含义(咱们从最容易理解的开始),后面讨论下一致性和原子性之间的关系、一致性和隔离性之间的关系。

一、四大特性

持久性,是指事务执行完毕之后,所做的修改能被保存起来,这个很容易理解,但凡是重要的东西,都要像档案一样保存起来,这是我们习惯的做法。

隔离性,是指两个事务之间互不干扰,我改我的,你改你的,咱们之间互不干扰,这也容易理解,彼此分清职责与界限,以免出问题时互相甩锅,人之常情嘛。不过这里有个问题,一定要互相隔离吗,如果不隔离会怎么样?我们先放着这个问题,后面再揭露谜底。

原子性,是指事务的一连串动作要么都完成,要么都不完成。用老江湖的话来说,那就是:“这件事咱们要干就把它干完干好,要么就不干!”

一致性,是指事务执行完毕之后,系统从一个状态转变为另一个状态,仍旧保持一致,没有中间状态。这句话什么意思?听起来和原子性很像哦,系统要么从一个状态变成另一个状态,要么不变,这可不就是原子性了吗?如果你有这个疑惑,那咱们就可以继续往下了。

仔细回想一下,在何时何地我们曾经遇到或听到过“不一致”这个词?
好像,数据库和缓存之间会不一致,刷写不及时,改完数据库还没来得及改缓存,或者是由于并发,缓存和数据库都扣减库存之后,发现总库存不对。
好像,mysql主从之间会不一致,俗称主从延迟,写进主库之后,从库里查不到。
那么一致性到底是什么意思呢?我们先放着这个问题,后面再揭露谜底。

二、事务的本质

在思考一致性之前,我们先来想一下事务的本质是什么。

从群论的角度看,事务本质是一连串动作的集合。在指挥官发号施令之后,众多动作接二连三地执行,“兄弟们冲啊”,直到最后一个动作执行完毕。如果有多个动作,把它们包装成事务,以至于从外界看起来是同一个动作,这是我们提出事务最根本的原因:把多个动作包装成一个动作,many->one。

但是,俗话说:“一个和尚挑水喝,两个和尚抬水喝,三个和尚没水喝”。一个团队中只有一个的人的时候还好,自力更生,奋发图强。但是林子大了什么鸟都有,团队大了什么人都有,很难做到众志成城齐心协力,力往一处使地干好一件事。要想抽象化团队中所有人,把他们当做一个人来,是很困难的。扯这么多,就是想说明事务说起来简单,做起来困难。

下面我们以商品交易业务作为例子,探讨一下事务的一致性。

三、一致性

3.1 单个事务的一致性

假设我们有一个事务,里面是扣减库存的业务逻辑。要扣减某商品1个库存,那么剩余库存减1,已售库存加1,最后剩余库存和已售库存之和(即总库存)仍等于扣减之前的总库存。我们发现,执行完前面一系列动作(事务)之后,总库存不变,执行前后总库存数值保持一致。

这种在事务执行前后总库存不变的特性(一致性),由什么来保证呢?仔细想一想会发现,其实是原子性,减剩余和加已售2个动作,要么全执行,要么全都不执行。

小结:单个事务前后的一致性,可以由原子性保证

3.2 多个并发事务的一致性

假设剩余20个库存,有2个事务同时执行,第一个事务要扣3个库存后来由于某种原因要回滚,第二个要扣10个库存,如果处理得不好,可能会出现下面的时序:
A)第一个事务查询到仍有20个库存
B)第一个事务扣3个库存,将剩余库存置为17
C)第二个事务查询到剩余17个库存,扣10个库存,将剩余库存置为7个
D)第一个事务回滚,将剩余库存置为20个。
两个事务执行完毕之后,剩余20个库存,和我们预想中的10个剩余库存不一致!

怎么样能让最终结果与我们预想的一样呢?可以考虑让两个事务隔离开来,用最严格的方法也就是串行化,先后执行2个事务。

小结:多个事务的并发一致性,可以由隔离性保证

四、小结

虽然第三部分两个小结中指出,事务的前后并发一致性分别由原子性和隔离性保证,但这并不说明一致性=原子性+隔离性,仅仅描述一致性、原子性和隔离性之间的关系