我们知道,事务有四个基本特性:ACID;
ACID 特性中的 AID 都很好理解,约等于字面意思,唯独 C(一致性)
众说纷纭,几乎是各有各的说法,这里简单说两句村长的理解吧:
我们先来看一下ACID具体指什么。
关于事务的ACID:
- Atomicity ) 原子性: 事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全不起作用;
- Consistency)一致性: 将数据库的数据,从【一个有效的状态】转移成【另一个有效的】状态。
- Isolation)隔离性: 并发访问数据库时,一个事务不被其他事务所干扰。
- Durability)持久性: 一个事务被提交之后。对数据库中数据的改变是持久的,即使数据库发生故障。
从上可以看出,AID 都很好理解,几乎没什么歧义,但一致性这个解释,会让很多人搞不清楚。
关于事务的一致性,比较官方的说法是:指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态,挺拗口的,理解上也很迷惑。
这里给出一个简洁版答案,当然,如果有耐心看完下面的解读更好。
先说一个大家估计都听过的定律:能量守恒定律(热力学第一定律);
- 能量既不能凭空产生,也不能凭空消灭,它只能从一种形式转化为另一种形式,或者从一个物体转移到另一个物体,在转移和转化的过程中,能量的总量不变。
以网上购物举例来说,商品出库后库存量-1
,这个1
库存不能凭空消失,需要让顾客的购买列表+1
此商品才能构成一个完整事务!
说白了就是,单纯执行-1
的库存无需引入事务,这不符合事务的定义,直接执行即可。
从这里也可以看出,这个一致性,其实更偏向于事务使用者所添加的一些外部约束,而非MySQL本身自动修正;
再来看看维基百科的英文原版:
In database systems, consistency (or correctness) refers to the requirement that any given database transaction must change affected data only in allowed ways. Any data written to the database must be valid according to all defined rules,
including constraints, cascades, triggers, and any combination thereof
. Thisdoes not guarantee correctness of the transaction in all ways the application programmer might have wanted
(that is the responsibility of application-level code) but merely that any programming errors cannot result in the violation of any defined database constraints.
Consistency can also be understood as after a successful write, update or delete of a Record, any read request immediately receives the latest value of the Record
.
在数据库系统中,一致性是指任何给定的数据库事务必须以被允许的方式更改受影响的数据。根据所有定义的规则,写入数据库的任何数据都必须有效,包括约束、级联、触发器及其任何组合
。这并不能保证应用程序程序员所希望的所有事务的正确性
(这是应用程序的代码的责任),而仅仅是任何编程错误都不会导致违反任何定义的数据库约束;
一致性也可以理解为在成功写入、更新或删除记录后,任何读取请求都会立即收到记录的最新值。
参考链接: https://en.wikipedia.org/wiki/Consistency_(database_systems)
从英文版的解释可以看出,这个所谓的一致性,其实是指数据库自己可控制的范围,而非通过应用程序代码去控制的范围,当然,也可以使用代码控制。
简单点儿说就是:所有可能违反现有数据库约束规则的事务提交都不会被执行。
并且,通过这两段原文可以看出,一致性,其实会细分为【写一致性】和【读一致性】;
写一致性
比如购物场景,如果用户下单买苹果时,去查库存apple_cnt
,发现库存0
个,但是代码逻辑直接这么写:apple_cnt = apple_cnt-1
,如果数据库没有添加任何约束,则apple_cnt
会被更新为-1
,这个很明显不符合现实生活中的逻辑。
如果你对数据库的库存量添加了约束,约束只能大于等于0,那么此事务就不会被执行,数据库不会出现-1
的库存量,因为它违反了事务的一致性规则(-1
并不是一个有效的数据状态)。
再看另一个场景:
- 1、有两个表(员工表 & 职位表),员工表中有员工ID、姓名、职位ID等属性,职位表中有职位ID、职位名称、职级等属性;
- 2、为员工表创建了职位ID的外键约束,依赖于职位表的ID;
- 3、向员工表中插入一个新员工,但职位ID却不存在于职位表(公司新创建的职位,但尚未入库);
此时,如果没有外键一致性的保证,就会出现,虽然新增了一个员工,但却不知道他是什么职位的情况!
读一致性
读一致性也是数据库一致性的一个重要方面:
我们对一个表中的某些数据进行了更新操作,但还没有进行提交,这时另外一个用户读取表中数据,就会出现读一致性的问题:到底该读什么时候的数据,是更新前的还是更新后的?
在DBMS中设有临时表,它用来保存修改前的值,在没有进行提交前读取数据,会读取临时表中的数据,这样一来就保证了数据是一致的(当前用户看到的是更新前的值);
还有一种情况:
用户user1对表进行了更新操作,用户user2在user1还没有进行提交前读表中数据,而且是大批量的读取(打个比方,耗时3分钟),而在这3分钟内user1进行了提交操作,那又会产生什么影响呢?这个时候怎么保证读写一致性呢?
这个时候DBMS就要保证有足够大的临时表来存放修改前的数值,以保证user2读取的数据是修改前的一致数据,然后下次再读取时候就是更新后的数据了。
下面为官方给出的定义做个简单 Summary 吧!还是以最开始提到的购物场景为例:
写一致性:
- 如果设定了库存量为无符号整数,那么当库存量为
0
时继续执行减1
的事务,就会违背事务的一致性原则,因为库存量不能为负数; - 相反,如果未设定,则会正常提交,因为MySQL不能保证程序员所希望的所有事务的正确性(这是应用程序的代码的责任);
读一致性:
- 如果设定了库存量为无符号整数,那么只要事务提交成功了,提交前后拿到的数据都可以放心使用(都是有效数据),因为MySQL限制了它只能大于等于0,相当于MySQL对用户做了保证,只要能提交成功,那么数据就是有效的;
- 相反如果未添加约束,就不能保证了,但MySQL可以保证它至少是个有效数字。
题外话 (不建议直接讲给面试官)
其实这个所谓的一致性的定义真的挺有歧义的,它的最终体现还是在原子性,毕竟违反了一致性,就会提交失败,而失败的连锁反应就是违背了原子性;
但原子性应该更侧重于客观原因的失败情况,即异常情况主动触发原子性原则,比如连接中断,因为直接将一致性的场景套用在原子性上是无法 cover 的;
比如库存量设定为无符号整数,更新为2、1、0
都没有问题,当更新为-1
时,至少在实际执行前是不知道它会失败的,这也许就是将【一致性】独立出来的原因之一吧。
关于 读一致性 的详细介绍可参考 INNODB锁 的并发问题