1.前言
事务是数据库区别于文件系统的重要特性之一,InnoDB存储引擎中的事务符合ACID的特性,即
- 原子性(atomicity)
- 一致性(consistency)
- 隔离性(isolation)
- 持久性(durability)
本文参考了姜承尧先生的《MySQL技术内幕InnoDB存储引擎》一书
2.事务四大特性
A(Atomicity)原子性:指整个数据库事务是不可分割的工作单位。只有使事务中的所有操作都执行成功了才能算整个事务执行成功,过程中只要有一个操作失败,就要回滚事务。
C(Consistency)一致性:指事务将数据库从一种状态转变为下一种一致的状态。在事务开始前和事务结束后,数据库的完整性约束没有被破坏。例如,一个列为id的字段,要求不能有重复id,但是事务结束后,出现了相同id,这就破坏了一致性
I(Isolation)隔离性:隔离性还有其他称呼,如并发控制、可串行化、锁等等。当事务A操作对象A时,其他事务不能操作A(隐身)。
D(Durability)持久性:一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便在数据库系统遇到故障的情况下也不会丢失事物的操作。
其中隔离性会产生的几种问题:
1、脏读
2、不可重复读
3、幻读
4、丢失更新
(这几个的概念在上一篇InnoDB锁中已经解释过了)
3.事务的分类
- 扁平事务
- 带有保存点的扁平事务
- 链事务
- 嵌套事务
- 分布式事务
3.1 扁平事务
扁平事务是事务类型最简单的一种,在扁平事务中,所有操作都处于同一层次,由BEGIN WORK 开始,由COMMIT WORK 或者ROLLBACK WORK 结束,期间操作是原子性的,要么都完成要么都回滚。
3.2 带有保存点的扁平事务
有时候全部操作回滚的代价比较高,例如买三张转机飞机票去北极,前两张都买好了,等买第三张的时候,机场告诉你票都抢没了,你需要买第二天的票,这时候回滚全部飞机票代价比较大,所以就需要一个带节点的扁平事务,只需回滚到第二张飞机票即可。
概念:允许事务执行过程中回滚到同一事务中较早的一个状态。保存点(Savepoint)用来通知系统保存当前事务状态,以便之后发生错误回滚到当前节点。
3.3 链事务
带有保存点的扁平事务是易失的,当发送系统崩溃的时候所以保存点消失。
链事务思想:在提交事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。提交事务操作和开始下一事务操作将合并为一个原子操作,意味着下一个事务将看到上个事务的执行结果。链事务与带有保存点的事务不同的是, 带有保存点的扁平事务能回滚到任意正确的保存点, 而链事务中的回滚仅限于当前事务. 另外, 链事务在执行了COMMIT后即释放了当前事务所持有的锁, 而带有保存点的扁平事务不影响迄今为止所持有的锁
3.4 嵌套事务
嵌套事务是一个层次结构框架,由一个顶层事务控制着各个层次的事务,子事务既可以提交也可以回滚,但是它的提交不会立马生效,需要顶层事务也提交,任意事务的回滚都会引起子事务的回滚。
3.5 分布式事务
通常是一个在分布式环境下运行的扁平事务。
4.事务的实现
事务隔离性由锁实现,原子性、一致性、持久性通过数据库的redo log 和 undo log 来完成。redo log称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。redo 和 undo 都视为一种恢复操作,redo 恢复提交事务修改的页操作,而undo回滚行记录到某个特定版本。redo 是时物理日志,记录的时页的物理修改操作,undo 是逻辑日志,记录每行。
4.1 redo
- 4.1.2 概述
重做日志用来实现事务的持久性。
组成:
- 内存中的重做日志缓冲(redo log buffer),是易失的。
- 重做日志文件(redo log file),是持久的。
InnoDB 是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性,当事务提交时,必须先将该事务的所有日志写到重做日志文件进行持久化,待事务的提交操作完成才算完成。
redo log 用来保证事务的持久性,undo log 用来帮助事务回滚及MVCC的实现。
为了保证每次日志都写入重做日志文件, 在每次将重做日志缓冲写入日志文件后, InnoDB都需要调用一次fsync操作. 重做日志缓冲先写入文件系统缓冲, 为了 确保重做日志写入磁盘, 必须进行一次fsync操作. 由于fsync的效率取决于磁盘的性能, 因此磁盘的性能决定了事务提交的性能.
- 4.1.2 log block
在InnoDB中, 重做日志都是以512字节进行存储的。这意味着重做日志缓冲, 重做日志都是以块的方式进行保存的. 称之为重做日志块(redo log block)每块大小512字节
- 4.1.3 log group
log group 为重做日志组,其中有多个重做日志文件。log group 是逻辑上的概念,并没有实际的物理存储,每个log group的日志文件大小是相同的,重做日志文件中存储的是之前在log buffer 中保存的log block,因此也是以块的方式进行物理存储管理,每个块大小也是512字节。
log buffer把log block刷新到磁盘根据一下规则:
- 事务提交时
- 当 log buffer 中有一半的内存空间已经被使用
- log checkpoint 时
对于log block的写入追加在redo log file 的最后部分,当redo log file 被写满时,会接着写入下一个redo log file
- 4.1.4 恢复
InnoDB存储引擎在启动时不管上一次数据库运行是否正常关闭, 都会尝试进行恢复操作. 因为重做日志记录的是物理日志, 因此恢复的速度比逻辑日志, 如二进制日志, 要快很多. 我理解的物理日志就是说 : 直接记录事务对于B+树结构和节点值的修改. 而逻辑日志是记录事务的每一句SQL语句, 事后再执行一遍还是需要改变B+树的结构. 所以物理日志比二进制日志要恢复得快
4.2 undo
- 4.2.1 概述
数据库修改时不仅会产生 redo ,也会产生一定的undo,这样如果用户执行的事务失败了需要回滚,或者用户想要执行ROLLBACK语句,这就可以用到 undo 将数据回滚到修改之前的样子。
redo 存放在重做日志中,undo 存放在数据库内部的一个特殊段中,称为 undo 段。undo段位于共享表空间内
undo是逻辑日志,所以回滚的时候并不是物理地恢复到原来的样子,而是InnoDB执行相反的语句来实现回滚,例如:当你插入10条语句INSERT ,此时回滚就会执行10条相反的DELETE语句来删除之前执行的插入,不使用物理方法的原因是数据库是并发的,在事务回滚过程中还会有其他事务进行数据处理,所以不能单纯地以物理的手段去回滚。
undo 的另一个作用是实现MVCC,当用户读取一行记录的时候,若该记录已被其他事务占用,当前事务可以通过 undo读取到之前的行版本信息,实现非锁定读取。
- 4.2.2 group commit
若事务非只读事务, 每次提交事务时需要进行一个fsync操作, 以此保证重做日志文件都已经写入了磁盘. 当数据库发生宕机时, 可以通过重做日志文件恢复. 磁盘的fsync性能是有限的, 当前数据库提供了group commit的功能, 即以此fsync操作确保多个事务日志被写入文件. 对于InnoDB来说, 事务提交是会进行两个阶段的动作 :
- 修改内存中事务对应的信息, 并且将日志写入重做日志缓冲
- 调用fsync将确保日志都从重做日志缓冲写入磁盘
有了group commit, 可以将多个事务的重做日志通过一次fsync操作就刷新到磁盘, 这样就大大减小了磁盘的压力
- 4.2.3 Binary Log Group Commit(BLGC)
在MySQL数据库上层进行提交时首先按照顺序将其放入一个队列中, 队列中的第一个 事务叫做leader, 其他事物称为follower, leader控制着flowwer的行为
BLGC实现方式是将事务提交的过程分为几个步骤来完成
- Flush阶段 , 将每个事务的二进制日志写入内存中
- Sync阶段, 将内存中的二进制日志刷新到磁盘, 若队列中有多个事务, 那么一次fsync操作就完成了二进制日志的写入
- Commit阶段, keader根据顺序调用存储引擎事务的提交
当有一组事务在进行 Commit阶段时, 其他事务 可以进行Flush阶段
5.数据库提供的四种隔离级别:
- Read uncommitted(读未提交):最低级别,任何情况都会发生。
- Read Committed(读已提交):可避免脏读的发生。
- Repeatable read(可重复读):可避免脏读、不可重复读的发生。
- Serializable(串行化):避免脏读、不可重复读,幻读的发生。
InnoDB的默认隔离级别为REPEATABLE READ, 但是与标准SQL不同的是, InnoDB存储引擎在REPEATABLE READ事务隔离级别下, 使用Next-Key Lock锁的算法, 因此避免幻读的产生. 但是其会出现丢失 更新的现象, 若要避免丢失更新, 还是需要SERIALIZABLE的隔离级别