目录
事务有ACID特性:A原子性、C一致性、I隔离性、D持久性。InnoDB存储引擎中,redo log(重做日志文件,称为日志)实现原子性和持久性;锁实现隔离性;undo log(回滚日志)实现一致性。
如下表所示,ACID特性的对比。事务的持久性则采用Force Log at Commit机制实现,即:事务提交时,必须先将该事务的所有日志写入到redo log(重做日志文件)进行持久化,然后事务提交后才算真正完成。
特性 | 概念 | 实现 |
原子性 (A _ Atomicity) | 1. 事务中的所有SQL语句,要么都成功或失败; 2. 事务是不可分割的工作单位。 | redo log |
一致性 (C _ Consistency) | 1. DB从一种状态转到另一个一致的状态; 2. 事务是一致性的单位。 | undo log |
隔离性 (I _ Isolation) | 事务提交之前对其他事务都不可见 | lock |
持久性 (D _ Durability) | 事务提交后即使发生宕机,则也是永久性的 | redo log |
InnoDB存储引擎支持扁平事务、保存点的扁平事务、链事务、分布式事务,原生不支持嵌套事务,下图事务分类总结。
类型 | 特点 |
扁平事务 | 1. 所有操作处于同一层次,不能提交部分操作,其是最频繁的事务; 2. 应用程序中原子操作的基本组成模块。 |
带有保存点的扁平事务 | 1. 支持扁平事务外,还支持回滚到某个保存点。 |
链事务 | 1. 事务提交后隐式地开启下一个事务; 2. 事务提交和开启下一个事务为一个原子操作; 3. 链事务回滚仅限于当前事务,即:只能回滚到最近一个保存点; 4. 链事务:提交后释放当前事务所持有的锁; 带有保存点的扁平事务:不影响迄今为止所持有的锁。 |
嵌套事务 | 1. 顶层事务控制子事务; 2. 任何子事务都在顶层事务提交后才真正的提交; 3. 子事务仅保留A、C、I特性,不具有D特性; 4. 嵌套事务中任意一个事务回滚,则所有子事务一同回滚。 |
分布式事务 | 1. 多个独立的事务资源参与到一个全局事务中; 2. InnoDB存储引擎支持XA事务(主从复制) |
扁平事务(Flat Transaction)指所有操作处于同一层次中,由BEGIN开始,COMMIT或ROLLBACK结束,实际生产中使用最频繁的事务。因此,扁平事务是应用程序成为原子操作的基本组成模块。
如下图所示,扁平事务中的三种情况。看出,扁平事务的主要限制不能提交或回滚事务的一部分。
带有保存点的扁平事务(Flat Transaction with Savepoint) 指事务执行过程中回滚到同一事务中较早的一个状态,也支持扁平事务。
保存点(Savepoint)用来通知系统记住当前事务的状态,以便之后发生错误时,事务能回滚到保存点当时的状态。注意,保存点在事务内部是单调递增,即使回滚后再开始操作,也不影响保存点的计数。如下图所示。
事务开始时,隐式地设置一个保存点。那么在扁平事务中,只有一个保存点,因此只能回滚到事务的开始状态。
链事务(Chained Transaction)指当前事务提交和开始下一个事务合并为一个原子操作。在当前事务提交后,释放数据对象,将必要的处理上下文隐式地传给下一个事务,即:下一个事务会看到上个事务的处理结果。
链事务与带有保存点的扁平事务的区别:
嵌套事务(Nested Transaction)指事务中嵌套事务,顶层事务(父事务)控制着各个层次的事务(子事务)。
嵌套事务的特点:
分布式事务(Distributed Transaction)指在多个独立的事务资源参与到一个全局事务中。InnoDB存储引擎对XA事务的支持。
XA事务由一个或多个资源管理器、一个事务管理器、一个应用程序组成:
XA分布式事务使用的是两阶段提交方式:
MySQL数据库中还存在另外一种分布式事务,不同存储引擎之间的事务,称为内部XA事务。最常见的内部XA事务存在于binlog与InnoDB存储引擎之间。在事务提交时,先写二进制日志,再写InnoDB重做日志,两个写入要求是原子操作,否则主从数据库存在数据不一致。如下图所示,数据库宕机发生在①、②之后,③之前,则会导致主从数据库存在数据不一致。
为了解决这个问题,如下图所示,采用XA事务。当事务提交时,InnoDB存储引擎会先做一个PREPARE操作,将事务xid写入。数据库发生宕机恢复后,先检查准备的UXID事务是否已经提交,若没有,InnoDB存储引擎层会再一次执行提交操作。
重做日志文件(redo log file)记录提交事务修改页的操作,是物理日志(默认数据目录下:ib_logfile0、ib_logfile1)。其目的:实现事务的原子、持久特性,数据库的恢复使用。
事务由两部分组成:
事务日志的产生过程:
需要注意的是:step3写入磁盘执行时间最慢,磁盘的性能决定事务提交的性能,也决定DB的性能。参数innodb_flush_log_at_trx_commit控制重做日志刷新到磁盘的策略,默认1(事务提交同步一次fsync) 。
事务的持久性则采用Force Log at Commit机制实现:事务提交时,必须先将该事务的所有日志写入到redo log file(重做日志文件)进行持久化,然后事务提交后才算真正完成,即:事务提交时,先写重做日志再提交事务。
重做日志(redo log)与二进制日志(binlog)的区别,如下表所示。
区别 | redo log | binlog |
产生层次 | InnoDB存储引擎层 | MySQL数据库层 (任何引擎都会产生) |
内容形式 | 物理日志,记录页的修改 | 逻辑日志,记录对应的SQL或行 |
写入磁盘的时机 | 1. 事务进行中不断的被写入磁盘,不是事务提交的顺序写入; 2. 每个事务对应多条日志,写入是并发的。 | 1.事务提交后完成一次写入; 2.按事务提交顺序写入,一个事务只能对应一个日志。 |
如下图所示,二进制日志对每一个事务仅保存一个日志,且按事务提交的顺序写入;重做日志对每个事务则对应多个日志条目(不同的页修改操作)且事务并发写入,不是事务提交的顺序写入。
重做日志格式组成如下图所示,可以看出redo log是记录页的修改操作。
根据不同的重做日志类型,会有不同的redo log body,如下图所示。
InnoDB存储引擎中,重做日志以512字节(块)存储,即:重做日志缓存、重做日志文件都是以块的形式进行保存,称之为“重做日志块”(redo log block)。重做日志块大小与磁盘扇区大小一样都是512字节,因此重做日志写入可以保证原子性,无需doublewrite技术。若修改页的重做日志大于512字节,则需要分割多个重做日志块进行存储。
重做日志块的组成:块512字节 = 头部12字节 + 内容 492(512-12-8)字节 + 尾部8字节,如下图所示。内容存储重做日志(重做日志格式见上小节)。
注意:其中LOG_BLOCK_FIRST_REC_GROUP表示块中第一个事务开始位置的日志偏移量, 如下图所示。
重做日志组(log group)是个逻辑概念,由多个重做日志文件组成。组内的文件大小相同,默认总大小512GB。默认情况下,一个InnoDB存储引擎只有一个日志组。
重做日志文件存储的就是重做日志缓冲里面的重做日志块,也是以块的形式管理。InnoDB存储引擎运行过程中,重做日志缓存刷新到磁盘的时机如下:
日志块追加到重做日志文件的尾部,当文件被写满时,则写入下一个重做日志文件,即:采用round-robin方式写入。
日志序列编号(Log Sequence Number _ LSN)表示写入重做日志的字节总数,占用8字节且单调递增。
LSN不仅在重做日志,而且还在每个页中。LSN的含义如下:
InnoDB存储引擎启动时,不管上次是否正常关闭,则都会尝试进行恢复数据操作。读取重做日志文件,根据数据页Checkpoint的LSN与重做日志文件中修改页的LSN进行比较,来判定数据页是否需要恢复操作。
重做日志是物理日志,则幂等。而二进制日志是逻辑日志,恢复慢。所以,重做日志恢复速度比二进制日志恢复快。
对数据库修改时,不仅会产生redo log,而且还会产生undo log。回滚日志(undo log)记录修改行的操作,是逻辑日志,存放在共享表空间的undo段(undo segment)。其目的:事务回滚,MVCC技术来实现事务一致性。
当InnoDB存储引擎回滚时,实际做的事与先前相反的工作,如下所示。
MVCC技术是通过undo log完成,当读取行时,判断该行是否被其他事务占用,若是则当前事务通过undo读取之前的行版本信息,实现非锁定读。
事务提交对undo log的处理:
需要注意的是,写入undo log日志的过程,也伴随着产生redo log,即:undo log也需要通过redo log来持久化。
回滚日志格式分类,如下:
注意,对于INSERT操作回滚时,则直接删除,符合事务的隔离性(只对当前事务可见,其他事务不可见),而DELETE/UPDATE操作,则放入链表等purge操作真正删除或修改;undo log是逻辑日志,只是将数据逻辑的恢复之前的样子,则回滚被逻辑的取消,但是表空间大小不会因回滚而收缩。
InnoDB存储引擎对undo segment的管理采用段的方式,支持同时在线的事务数量为:128 * 1024。
回滚段(rollback segment):一个undo段(undo segment)支持128个rollback segment
undo日志段(undo log segment):每个rollback segment有1024个undo log segment,在该段中进行undo页的申请
与rollback segment相关的参数有:
事务提交时,判定undo页是否重用:
当DELETE删除一条记录时,将记录delete flag设置为1,记录并没有删除,还存在于B+树中。所以DELETE和UPDATE操作并不直接删除或修改原有数据,而是“延迟”在purge操作中完成。因此purge用于完成DELETE、UPDATE操作。
purge用于完成DELETE、UPDATE操作,这样的设计InnoDB存储引擎支持MVCC,若该行记录不被其他事务引用,则可以真正删除操作。根据之前undo log的介绍,一个undo页有多个事务的undo log存在,后面的事务总在最后,但不是事务提交顺序。因此,InnoDB存储引擎有个history list,目的是按事务提交的顺序,将undo log进行链接(先提交放在尾端)。
如上图所示, purge操作的过程如下。清除undo page,避免大量随机读取操作,提高purge效率。
与purge有关的相关参数,innodb_purge_batch_size越大,每次回收undo页就越大,但是太大会导致CPU和磁盘过于集中处理导致性能下降。
参数 | 描述 |
innodb_purge_batch_size | 每次purge操作需要清理的undo page的数量,默认300 |
innodb_max_purge_lag | 控制history list的最大长度,默认0(不对history list做任何限制) |
innodb_max_purge_lag_delay | 更新行,最大延迟的毫秒数,默认0 |
对于非只读事务时,每次提交事务后需调用一次fsync操作,来保证redo日志已经写入磁盘,而对磁盘的操作效率很慢。因此MySQL提供组提交的功能,其目的是一次fsync刷新多个事务日志写入重做日志文件(提高磁盘fsync效率)。
MySQL5.6版本之前,开启二进制日志后,group commit功能会失效,而MySQL5.6采用BLGC(Binary Log Group Commit)实现方式。提交顺序放入队列,第一个为leader,其他follower,leader控制follower的行为,如下图所示。BLGC的三个阶段:
当有一组事务在进行Commit阶段时,其他新事务可以进行Flush阶段,从而使group commit不断生效。参数binlog_max_flush_queue_time控制group commit时的flush阶段中等待时间,默认0(不等待),当一组事务完成提交,当前一组事务也不马上进入Sync阶段,而是等待一段时间。
SQL标准定义的四个隔离级别为:
REPEATABLE READ是2.9999⁰的隔离,没有幻读的保护;而SERIALIABLE称为隔离,或是3⁰的隔离。SQL和SQL2标准默认事务隔离级别是SERIALIABLE。
需要注意的是InnoDB存储引擎默认事务隔离级别是REPEATABLE READ,与SQL标准不同的是,InnoDB存储引擎在该级别下使用Next-Key Lock锁的算法,避免的不可重复读,已经达到了事务的隔离性要求,即达到了SERIALIABLE的隔离性要求。
InnoDB存储引擎的隔离性是通过锁实现的。READ COMMITTED隔离级别,则采用Record Lock算法,关闭了Gap Lock;REPEATABLE READ隔离级别时,采用Next-Key Lock(Record Lock + Gap Lock);SERIALIABLE隔离级别时,对每个SELECT语句自动加上LOCK IN SHARE MODE,加共享锁,因此读占用了锁,对一致性非锁定读不支持,此时隔离级别符合数据库理论的要求。
隐式提交是指执行完这些SQL语句后,会有一个隐式COMMIT操作。如下所示,列出隐式提交有哪些SQL语句。注意TRUNCATE TABLE与DELETE一样,但是不能回滚。