关系型数据库中不可不知的事务特性

293 阅读6分钟

一、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特性及其实现原理:

  1. 原子性:语句要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的;实现主要基于undo log
  2. 持久性:保证事务提交后不会因为宕机等原因导致数据丢失;实现主要基于redo log
  3. 隔离性:保证事务执行尽可能不受其他事务影响;InnoDB默认的隔离级别是RR,RR的实现主要基于锁机制(包含next-key lock)、MVCC(包括数据的隐藏列、基于undo log的版本链、ReadView)
  4. 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障

二、隔离级别

1、脏读、不可重复读和幻读

  1. 脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据)
  2. 不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样
  3. 幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同

2、Read Uncommitted (读未提交)

会出现脏读、不可重复读、幻读

3、Read Committed (读已提交)

会出现 不可重复读、幻读

4、Repeatable Read (可重复读)

会出现幻读

5、Serializable(可串行化)

    强制事务串行,并发效率很低

InnoDB默认的隔离级别是RR;在SQL标准中,RR是无法避免幻读问题的,但是InnoDB实现的RR避免了幻读问题,其实现了mvcc控制

三、锁机制

  数据库存储引擎,MyIsam只支持表锁,而InnoDB同时支持表锁和行锁

四、MVCC(多版本并发控制协议)

特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)

实现原理:

  1. 隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。

  2. 基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链。

  3. 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可见