MySQL探秘(八):InnoDB的事务

 事务是数据库最为重要的机制之一,凡是使用过数据库的人,都了解数据库的事务机制,也对ACID四个基本特性如数家珍。可是聊起事务或者ACID的底层实现原理,每每言之不详,不明因此。因此,今天咱们就一块儿来分析和探讨InnoDB的事务机制,但愿能创建起对事务底层实现原理的具体了解。mysql

事务的四大特性

 数据库事务具备ACID四大特性。ACID是如下4个词的缩写:算法

  • 原子性(atomicity) :事务最小工做单元,要么全成功,要么全失败 。
  • 一致性(consistency): 事务开始和结束后,数据库的完整性不会被破坏 。
  • 隔离性(isolation) :不一样事务之间互不影响,四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。
  • 持久性(durability) :事务提交后,对数据的修改是永久性的,即便系统故障也不会丢失 。

 下面,咱们就以一个具体实例来介绍数据库事务的原理,并介绍InnoDB是如何实现ACID四大特性的。sql

示例介绍

 咱们首先来看一下具体的示例。你们能够本身亲自试验一下,这样理解和记忆都会更加深入。数据库

 首先,使用以下的SQL语句建立两张表,分别是goods和trade,表明货物和交易。并向goods表中插入一条记录,id为1的货物数量为10。缓存

CREATE TABLE goods (id INT, num INT, PRIMARY KEY(id));
CREATE TABLE trade (id INT, goods_id INT, user_id INT, PRIMARY KEY(id));
INSERT INTO goods VALUES(1, 10);
复制代码

 而后打开终端,链接数据库,开启会话一,先用BEGIN显示开启一个事务。会话一先将goods表中id为1的货物的数量减一,而后向trade表中添加一笔交易的记录,最后使用COMMIT显示提交事务。bash

 而会话二则先查询goods表中id为1的货物数量,而后向trade表中添加一笔交易记录,接着更新goods表中id为1的货物的数量,最后使用ROLLBACK进行事务的回滚。其中,两个会话中执行的具体语句和前后顺序以下图所示。并发

示例具体语句和执行顺序

 这个示例能够体现数据库事务的不少特性,咱们一一来介绍。首先会话一的操做2更新了id为1的货物的数量,可是会话二的操做5读出来的数量仍然是10,这体现了事务的隔离性,使用InnoDB的多版本控制机制实现。性能

 会话二的操做7也要更新同种货物的数量,此时由于会话一的操做2已经更新了该货物的数量,InnoDB已经锁住了该记录的行锁,因此操做7会被阻塞,直到会话一COMMIT。可是会话一的操做4和会话二的操做7都是向trade表中插入记录,后者却不会由于前者而阻塞,由于两者插入的不是同一行记录。锁机制是一种常见的并发控制机制,它和多版本控制机制一块儿实现了InnoDB事务的隔离性,关于InnoDB锁相关的具体内容能够参考InnoDB锁的类型和状态查询InnoDB行锁算法atom

 会话一事务最终使用COMMIT提交了事务而会话二事务则使用ROLLBACK回滚了整个事务,这体现了事务的原子性。即事务的一系列操做要么所有执行(COMMIT),要么就所有不执行(ROLLBACK),不存在只执行一部分的状况。InnoDB使用事务日志系统来实现事务的原子性。这里有的同窗就会问了,若是中途链接断开或者Server Crash会怎么样。能怎么样,直接自动回滚呗。spa

 一旦会话一使用COMMIT操做提交事务成功后,那么数据必定会被写入到数据库中并持久的存储起来,这体现了事务的持久性。InnoDB使用redo log机制来实现事务的持久性。

 而事务的一致性比较难以理解,简单的讲在事务开始时,此时数据库有一种状态,这个状态是全部的MySQL对象处于一致的状态,例如数据库完整性约束正确,日志状态一致等。当事务提交后,这时数据库又有了一个新的状态,不一样的数据,不一样的索引,不一样的日志等。但此时,约束,数据,索引,日志等MySQL各类状态仍是要保持一致性。 也就是说数据库从一个一致性的状态,变到另外一个一致性的状态。事务执行后,并无破坏数据库的完整性约束。

 下面咱们就来详细讲解一下上述示例涉及的事务的ACID特性的具体实现原理。总结来讲,事务的隔离性由多版本控制机制和锁实现,而原子性、一致性和持久性经过InnoDB的redo log、undo log和Force Log at Commit机制来实现

原子性,持久性和一致性

 原子性,持久性和一致性主要是经过redo log、undo log和Force Log at Commit机制机制来完成的。redo log用于在崩溃时恢复数据,undo log用于对事务的影响进行撤销,也能够用于多版本控制。而Force Log at Commit机制保证事务提交后redo log日志都已经持久化。

 开启一个事务后,用户可使用COMMIT来提交,也能够用ROLLBACK来回滚。其中COMMIT或者ROLLBACK执行成功以后,数据必定是会被所有保存或者所有回滚到最初状态的,这也体现了事务的原子性。可是也会有不少的异常状况,好比说事务执行中途链接断开,或者是执行COMMIT或者ROLLBACK时发生错误,Server Crash等,此时数据库会自动进行回滚或者重启以后进行恢复。

 咱们先来看一下redo log的原理,redo log顾名思义,就是重作日志,每次数据库的SQL操做致使的数据变化它都会记录一下,具体来讲,redo log是物理日志,记录的是数据库页的物理修改操做。若是数据发生了丢失,数据库能够根据redo log进行数据恢复。

 InnoDB经过Force Log at Commit机制实现事务的持久性,即当事务COMMIT时,必须先将该事务的全部日志都写入到redo log文件进行持久化以后,COMMIT操做才算完成。

 当事务的各类SQL操做执行时,即会在缓冲区中修改数据,也会将对应的redo log写入它所属的缓存。当事务执行COMMIT时,与该事务相关的redo log缓冲必须都所有刷新到磁盘中以后COMMIT才算执行成功。

数据库日志和数据落盘机制

 redo log写入磁盘时,必须进行一次操做系统的fsync操做,防止redo log只是写入了操做系统的磁盘缓存中。参数innodb_flush_log_at_trx_commit能够控制redo log日志刷新到磁盘的策略,它的具体做用能够查阅InnoDB的磁盘文件及落盘机制

 redo log所有写入磁盘后事务就算COMMIT成功了,可是此时事务修改的数据还在内存的缓冲区中,称其为脏页,这些数据会依据检查点(CheckPoint)机制择时刷新到磁盘中,而后删除相应的redo log,可是若是在这个过程当中数据库Crash了,那么数据库重启时,会依据redo log file将那些还在内存中未更新到磁盘上的数据进行恢复。

 数据库为了提升性能,数据页在内存修改后并非每次都会刷到磁盘上。而是引入checkpoint机制,择时将数据页落盘,checkpoint记录以前的数据页保证必定落盘了,这样相关的redo log就没有用了(因为InnoDB redo log file循环使用,这时这部分日志就能够被覆盖),checkpoint以后的数据页有可能落盘,也有可能没有落盘,因此checkpoint以后的redo log file在崩溃恢复的时候仍是须要被使用的。InnoDB会依据脏页的刷新状况,按期推动checkpoint,从而减小数据库崩溃恢复的时间。检查点的信息在第一个日志文件的头部。

 数据库崩溃重启后须要从redo log中把未落盘的脏页数据恢复出来,从新写入磁盘,保证用户的数据不丢失。固然,在崩溃恢复中还须要回滚没有提交的事务。因为回滚操做须要undo日志的支持,undo日志的完整性和可靠性须要redo日志来保证,因此崩溃恢复先作redo恢复数据,而后作undo回滚。

 在事务执行的过程当中,除了记录redo log,还会记录必定量的undo log。undo log记录了数据在每一个操做前的状态,若是事务执行过程当中须要回滚,就能够根据undo log进行回滚操做。

数据和回滚日志的逻辑存储结构.jpg

 undo log的存储不一样于redo log,它存放在数据库内部的一个特殊的段(segment)中,这个段称为回滚段。回滚段位于共享表空间中。undo段中的以undo page为更小的组织单位。undo page和存储数据库数据和索引的页相似。由于redo log是物理日志,记录的是数据库页的物理修改操做。因此undo log的写入也会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是由于undo log也须要持久性的保护。如上图所示,表空间中有回滚段和叶节点段和非叶节点段,而三者都有对应的页结构。

 咱们再来总结一下数据库事务的整个流程,以下图所示。

事务的相关流程

 事务进行过程当中,每次sql语句执行,都会记录undo log和redo log,而后更新数据造成脏页,而后redo log按照时间或者空间等条件进行落盘,undo log和脏页按照checkpoint进行落盘,落盘后相应的redo log就能够删除了。此时,事务还未COMMIT,若是发生崩溃,则首先检查checkpoint记录,使用相应的redo log进行数据和undo log的恢复,而后查看undo log的状态发现事务还没有提交,而后就使用undo log进行事务回滚。事务执行COMMIT操做时,会将本事务相关的全部redo log都进行落盘,只有全部redo log落盘成功,才算COMMIT成功。而后内存中的数据脏页继续按照checkpoint进行落盘。若是此时发生了崩溃,则只使用redo log恢复数据。

隔离性

 InnoDB事务的隔离性主要经过多版本控制机制和锁机制实现,具体能够参考多版本控制InnoDB锁的类型和状态查询InnoDB行锁算法三篇文章。

后记

 原本想一篇文章将MySQL的事务机制讲明白,写完本身读了一遍,仍是发现内容有些晦涩难懂,复杂的知识原本就是很难讲明白的,夫夷以近,则游者众;险以远,则至者少,但愿读者以本文做为一篇指引性的文章,本身再去更加深刻的地方去探秘。不过,能将复杂知识讲解的通俗简单也是一项很大的本领,文字和讲解能力仍是须要提示的。

参考

相关文章
相关标签/搜索