InnoDB存储引擎:事务
认识事务
概述
事务:访问并更新数据库中各种数据项的一个程序执行单元。
数据库引入事务的主要目的:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。
InnoDB存储引擎中的事务完全符合ACID的特性:
- 原子性(atomicity)(整个数据库事务是不可分割的工作单位)
- 一致性(consistency)(事务将数据库从一种状态转变为下一种一致的状态)
- 隔离性(isolation)(要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见)
- 持久性(durability)(事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复)
分类
从事务理论的角度来说,可以把事务分为以下几种类型:
- 扁平事务(操作为原子级,要么都执行,要么都回滚)
- 带有保存点的扁平事务(除了支持扁平支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态)
- 链事务(在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务)
- 嵌套事务(层级结构框架,由一个顶层事务控制着各个层次的事务)
- 分布式事务(一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点)
链事务与带有保存点的扁平事务的不同点:带有保存点的扁平事务能回滚到任意正确的保存点,而链事务中的回滚仅限于当前事务,即只能恢复到最近的一个保存点;链事务在执行 COMMIT 后即释放了当前事务所持有的锁,而带有保存点的扁平事务不影响迄今为止所持有的锁。
嵌套事务注意事项:子事务既可以提交也可以回滚。但是它的提交并不马上生效,除非其父事务已经提交。意思就是任何子事务都在顶层事务提交后才真正的提交。
当然也可以用保存点技术来模拟嵌套事务,但是在锁的持有方面还是有些区别:通过保存点技术来模拟嵌套事务,无论有多少个保存点,所有被锁住的对象都可以被得到和访问;而在嵌套事务中,不同的子事务在数据库对象上持有的锁是不同的。
事务的实现
事务的隔离性由锁实现,原子性、一致性、持久性通过数据库的 redo log 和 undo log 来实现。
redo
基本概念
重做日志用来实现事务的持久性,其由两部分组成:
- 内存中的重做日志缓冲(redo log buffer)(易失)
- 重做日志文件(redo log file)(持久)
当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的 COMMIT 操作完成才算完成。
为了确保每次日志都写入重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次 fsync 操作。
log block
重做日志块(log block):在InnoDB存储引擎中,重做日志文件都是以块的方式进行保存的,每块的大小为512字节。
若一个页中产生的重做日志数量大于 512 字节,那么需要分割为多个重做日志块进行存储。由于重做日志块的大小和磁盘扇区大小一样,因此重做日志的写入可以保证原子级。
下图为重做日志缓存的结构:
由上图可知,日志块由三个部分组成:
- 日志块头(log block header)
- 日志内容(log body)
- 日志块尾(log block tailer)
log group
log group 为重组日志组,其中有多个重做日志文件,InnoDB存储引擎实际只有一个 log group。
log group 是一个逻辑的概念,并没有一个实际存储的物理文件来表示 log group 信息。log group 由多个重做日志组成,每个 log group 中的日志文件大小是相同的。
重做日志格式
不同的数据库操作会有对应的重做日志格式。由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的。重做日志有着通用的头部格式:
redo_log_type | space | page_no | redo log body |
---|
通用的头部格式由以下3部分组成:
- redo_log_type:重做日志类型
- space:表空间的ID
- page_no:页的偏移量
LSN
LSN 是 Log Sequence Number 的缩写,其代表的是日志序列号。在InnoDB存储引擎中,LSN占用8字节,并且单调递增。LSN表示的含义有:
- 重做日志写入的总量
- checkpoint的位置
- 页的版本
LSN表示事务写入重做日志的字节的总量;LSN不仅记录在重做日志中,还存在于每个页中。
恢复
InnoDB存储引擎在启动时不管上次数据库运行时是否正常关闭,都会尝试进行恢复操作。
undo
基本概念
当事务需要进行回滚操作时,就需要用到 undo。
redo 存放在重做日志文件中,与 redo 不同,undo 存放在数据库内部的一个特殊段中,这个段称为 undo 段。
需要注意的是 undo 是逻辑日志,只是将数据库逻辑地恢复到原来的样子。
undo log 格式
在InnoDB存储引擎中,undo log 分为:
- insert undo log(在 insert 操作中产生的 undo log。由于 insert 操作的记录,只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除)
- update undo log(对 delete 和 update 操作产生的 undo log。该 undo log 可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除)
purge
delete 和 update 操作可能并不直接删除原来的数据,故而 purge 用于最终完成 delete 和 update 操作。
group commit
若事务为非只读事务,则每次事务提交时需要进行一次 fsync 操作,以此保证重做日志都已经写入磁盘。为了提高磁盘 fsync 的效率,当前数据库都提供了 group commit 的功能, 即一次 fsync 操作可以刷新确保多个事务日志被写入文件。
事务的隔离级别
SQL 标准定义的四个隔离级别为:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read-uncommitted(读未提交) | 是 | 是 | 是 |
read-committed(不可重复读) | 否 | 是 | 是 |
repeatable-read(可重复读) | 否 | 否 | 是 |
serializable(串行化) | 否 | 否 | 否 |
InnoDB存储引擎默认的事务隔离级别为 repeatable-read(可重复读),在该隔离级别下,使用 Next-Key Lock 锁的算法避免幻读的产生。
分布式事务
分布式事务指的是允许多个独立的事务资源参与到一个全局的事务中,全局事务要求在其中的所有参与的事务要么都提交,要么都回滚。而且在使用分布式事务时,InnoDB存储引擎的事务隔离级别必须设置为 serializable(串行化)。
分布式事务使用两段式提交的方式:
- 第一阶段,所有参与全局事务的节点都开始准备,告诉事务管理器它们准备好提交了。
- 第二阶段,事务管理器告诉资源管理器执行 rollback 还是 commit;如果任何一个节点显示不能提交,则所有的节点都被告知需要回滚。
事务习惯
开发人员需要注意3点事务习惯:
- 不要在循环中提交(无论显式还是隐式)
- 不要使用自动提交
- 不要使用自动回滚