InnoDB如何保证事务四大特性

事务的四大特性

  1. 原子性(Atomicity):原子性指整个数据库事务是不可分割的工作单位。事务中所有的数据库操作,要么全部提交成功,要么全部失败回滚。

  2. 一致性(Consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。

  3. 隔离性(Isolation):事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见。

  4. 持久性(Durability):事务一旦提交,其结果是永久性的,即使发生宕机等故障,数据库也能将数据恢复。

并发一致性问题

  1. 丢失修改
  2. 脏读:读到未提交的数据
  3. 不可重复读:读到已提交的数据;同样的条件,第一次与第二次读的值不同。
  4. 幻读:同样的条件,第一次与第二次读出来的记录不同。

隔离级别

  1. READ UNCOMMITTED(读未提交):事务中的修改,即使没有提交,对其它事务也是可见的。任何操作都不加锁。

  2. READ COMMITTED(读已提交):一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。数据的读取不加锁,但数据的写入、修改和删除都加锁。

  3. REPEATABLE READ(可重复读):保证在同一个事务中多次读取同样数据的结果是一样的。MVCC机制让数据变得可重复读。

  4. SERIALIZABLE(可串行化):强制事务串行执行。全部操作加悲观锁。读加共享锁,写加排他锁。

Redo log

重做日志由两部分组成:内存中重做日志缓冲和重做日志文件。

当事务提交时,必须先将该事务的所有日志写入到重做日志进行持久化。在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync操作,来确保日志写入磁盘。由于fsync操作取决于磁盘,因此磁盘性能决定了事务提交的性能。

参数innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。该参数默认值为1,表示事务提交时必须调用一次fsync操作。参数为0表示事务提交时不进行写入重做日志操作,而在master thread中每秒进行一次写入重做日志,并执行fsync操作。参数为2表示事务提交时将重做日志写入重做日志文件,但仅写入缓存中,不进行fsync操作。

Undo log

如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用undo信息将数据回滚。undo存放在数据库内部的undo段(undo segment)中。

当InnoDB存储引擎回滚时,它实际上做的是与先前相反的工作。对于每个UPDATE,会执行一个相反的UPDATE,将修改前的行放回去。如果是INSERT,会执行DELETE,反之同理。所以,undo是逻辑日志而不是物理恢复。数据结构和页本身在回滚之后可能大不相同。不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。

InnoDB中的锁

行级锁

  • 共享锁(S Lock,Shared),允许事务读一行数据
  • 排他锁(X Lock,Exclusive),允许事务删除或更新一行数据
    一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。

一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。

兼容关系

- X S
X NO NO
S NO YES

表级锁:意向锁(Intention Lock)

  • 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁
  • 意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁

任意IS/IX锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁.
S锁只与S锁和IS锁兼容,也就是说事务T想要对数据行加S锁,其它事务可以已经获得对表或者表中的行的S锁。

兼容关系

- X IX S IS
X NO NO NO NO
IX NO YES NO YES
S NO NO YES YES
IS NO YES YES YES

一致性非锁定读

一致性非锁定读:如果一条记录被加了X锁,其他事务读取这条记录时,不会等待行上的锁释放,InnoDB存储引擎会去读取行的一个快照数据。

一个行记录可能有不止一个快照数据,一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control, MVCC)。

MVCC:为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

READ COMMITTED事务隔离级别时,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。
REPEATABLE READ事务隔离级别时,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

一致性锁定读

显示地对数据库读取操作进行加锁。

SELECT … FOR UPDATE

SELECT … LOCK IN SHARE MODE

行锁的三种算法

InnoDB存储引擎有三种行锁的算法。

  1. Record Lock:单个行记录上的锁。锁住索引记录(若建表时无索引,使用隐式的主键进行锁定)
  2. Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
  3. Next-Key Lock:是Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身(区间前开后闭)。

InnoDB查询时采用Next-Key Locking,解决了幻读。

InnoDB如何保证原子性(Atomicity)?

在事务里任何对数据的修改都会写一个undo log,然后进行数据的修改,如果出现错误,存储引擎会利用undo log的备份数据恢复到事务开始之前的状态。

InnoDB如何保证一致性(Consistency)?

事务的原子性和隔离性保证了数据的一致性

InnoDB如何保证隔离性(Isolation)?

InnoDB的默认隔离级别REPEATABLE READ + Next-Key Locking,保证了数据库的隔离性,不出现并发一致性问题,且理论上效率高于SERIALIZABLE隔离级别。

InnoDB如何保证持久性(Durability)?

InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试通过redo log进行恢复操作。

参考资料: