一、ACID特性
按照严格的标准,只有同时满足ACID特性才是事务;但是在各大数据库厂商的实现中,真正满足ACID的事务少之又少。例如MySQL的NDB Cluster事务不满足持久性和隔离性;InnoDB默认事务隔离级别是可重复读,不满足隔离性;Oracle默认的事务隔离级别为READ COMMITTED,不满足隔离性……因此与其说ACID是事务必须满足的条件,不如说它们是衡量事务的四个维度。
1、Atomiticty(原子性)
1.1 定义
事务是一个原子性质的操作单元,对数据库的SQL操作要么都执行,要么都不执行;在SQL执行失败后,会回退到事务&emsp执行之前的状态;
1.2 实现原理(undo log)
undo log(回滚日志)属于逻辑日志,它记录着sql执行的相关信息,当发生会回滚时,根据undo log 内容执行相反操作,回到执行事务前的状态;
2、Consistent(一致性)
2.1 定义
在事执行结束之后,数据库的完整性约束没有被破坏,执行事务前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。
2.2 实现原理
可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。
3、Isolation(隔离性)
3.1 定义
事务内部的操作是与其他事务隔离的,各个事务并发执行,互不干扰。
3.2 实现原理
- (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
- (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离性
4、Durable(持久性)
4.1 定义
事务一旦提交,它对数据的改变是永久性的,接下来的操作或故障不应该对其有任何影响。
4.2 实现原理
Mysql在读写数据的时,存储引擎(InnoDB)提供了缓存(Buffer pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲,提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。于是,重做日志(redo log)被引入来解决这个问题,,当数据修改时,除了修改Buffer Pool 的数据,还会在redo log 中记录此次操作,保证服务宕机,数据恢复不丢失。
5、总结
总结一下ACID特性及其实现原理:
- 原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log
- 持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log
- 隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制(包含next-key lock)、MVCC(包括数据的隐藏列、基于undo log的版本链、ReadView)
- 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障
二、隔离级别
1、脏读、不可重复读和幻读
- 脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据)
- 不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样
- 幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同
2、Read Uncommitted (读未提交)
会出现脏读、不可重复读、幻读
3、Read Committed (读已提交)
会出现 不可重复读、幻读
4、Repeatable Read (可重复读)
会出现幻读
5、Serializable(可串行化)
强制事务串行,并发效率很低
InnoDB默认的隔离级别是RR;在SQL标准中,RR是无法避免幻读问题的,但是InnoDB实现的RR避免了幻读问题,其实现了mvcc控制
三、锁机制
数据库存储引擎,MyIsam只支持表锁,而InnoDB同时支持表锁和行锁
四、MVCC(多版本并发控制协议)
特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)
实现原理:
-
隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。
-
基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链。
-
ReadView:通过隐藏列和版本链,MySQL可以将数据恢复到指定版本;但是具体要恢复到哪个版本,则需要根据ReadView来确定。所谓ReadView,是指事务(记做事务A)在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。
trx_sys中的主要内容,以及判断可见性的方法如下:
low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的id。如果数据的事务id大于等于low_limit_id,则对该ReadView不可见。
up_limit_id:表示生成ReadView时当前系统中活跃的读写事务中最小的事务id。如果数据的事务id小于up_limit_id,则对该ReadView可见。
rw_trx_ids:表示生成ReadView时当前系统中活跃的读写事务的事务id列表。如果数据的事务id在low_limit_id和up_limit_id之间,则需要判断事务id是否在rw_trx_ids中:如果在,说明生成ReadView时事务仍在活跃中,因此数据对ReadView不可见;如果不在,说明生成ReadView时事务已经提交了,因此数据对ReadView可见