MySQL 运行原理 [事务]
文章为《MySQL技术内幕:InnoDB存储引擎(第二版)》、掘金小册《MySQL是怎样运行的:从根儿上理解MySQL》 的笔记
事务概述
在现实生活中,大家都有过以下经历(也是事务的经典场景之一):
- 最近没钱了,需要去银行取些钱用,然后就去ATM机取钱
- 在ATM机取 100 块钱
- 银行程序会在将银行账户相应的减少 100 块钱
- 最终取到钱
以上步骤是在理想的情况下进行的,假如在第二步骤时ATM机突然断电/坏了呢,钱没拿到,而账户里却少了 100 块,是不是特别冤枉,再或者,假如在第三步骤的时候,银行服务器突然宕机了,用户拿到了钱,而账户资金却没有相应的减少,银行会相应的亏损 100 块。
在这样的情况下双方资金都得不到保障,这样对双方都是不利的。
事务的出现就是为了保证以上情景会正常运作,事务会把数据库的一种一致状态转换为另外一种一致状态,在数据库提交工作时,可以确保要么所有修改都保存,要么所以修改都不会保存,在上述场景中,假如ATM机突然断电了或者银行服务器宕机了,不论发生什么异常场景事务都会直接回滚,不会将所做的修改提交
ACID性质
其实我们只是想让数据库操作完全符合我们正常逻辑的状态转换而已,在InnoDB存储引擎中的事务也完全符合ACID的特性。
原子性(Atomicity)
众所周知原子是现实中物理最小的单位,不可再分割,在数据库中,原子性指的是整个数据库事务是不可分割单位,只有事务执行成功才算整个事务执行成功,若其中任何一条sql语句执行失败则整个事务都执行失败,执行成功的sql也将撤回,数据库状态应该退回事务开始之前的状态。
一致性(Consistency)
现实生活中有着许许多多的约束,比如我们的身份证号码不能重复,性别只有男女(正常来说),红绿灯只有三种颜色等等,那么在数据库中同样存在着约束,比如比一张表中有一列的索引为唯一索引(unique index)那么这一列不能有重复行数据,当然更多的一致性需求还是需要靠写业务代码的人来保证该一致性。最终一致性保证的是关注数据的可见性,中间状态的数据对外不可见,只有最初状态和最终状态的数据对外可见的。
隔离性(Isolation)
隔离性还有另外一个称呼“并发控制、可串行化、锁等等”现实世界中的两次状态转化应该是互不影响的,还是取钱场景举例,假如同时在两台ATM机取钱,只扣了一台ATM所取出的钱,这样是不合理的对吧,所以事务的隔离性要求每个读写事务的对象对其他事务操作的对象能相互独立分离,即事务提交前对其他事务不可见,通常用锁来实现。
永久性(Durability)
事务一旦提交了就不能反悔了,因为一旦事务提交那么结果就是永久性的,即使数据库发生宕机,数据也能恢复。
事务分类
- 扁平事务:最简单也是实际使用最频繁的一种事务,由
begin
开始,commit work
或者rollback
结束,期间的操作都是原子性操作,要么执行,要么回滚 - 带有保存点的扁平事务
- 除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态
- 保存点用来通知系统应该记住事务当前的状态,一旦事务过程中发生错误,事务能回到保存点当时的状态
- 链事务
- 可视为保存点模式的一种变种,带有保存点的扁平事务,当发生系统崩溃时,所有保存点都将消失,因为保存点是易失的而非持久的
- 这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的一个保存点继续执行
- 嵌套事务:由一个顶层事务控制着各个层次的事务,顶层事务之下嵌套的事务称为子事务,其控制着每个局部的变换
- 分布式事务:通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在的位置访问网络中的不同节点
事务实现
原子性、一致性、持久性通过数据库的 redo log
和 undo log
来完成,redo log
称作为重做日志,用来保证事务的原子性和持久性, undo log
用来保证事务的一致性。有些人可能会认为 undo log
是 redo log
的逆过程,其实不然,redo
和 undo
的作用都可以视为是一种恢复操作,redo
恢复提交事务修改的页操作,而undo回滚记录到某个特定的版本,因此两者记录的内容不同,redo
通常是物理日志,记录的是页的物理修改操作,undo
是逻辑日志,根据每行记录进行记录。
redo log
上面也提到了 redo log
就是为了事务的持久性(D),在事务提交后会将修改内容刷新至磁盘中,即使数据库宕机,在重启后会 redo log
记录的修改内容刷新至磁盘中去。
使用 redo log
的好处:
redo log
占用空间小redo log
顺序写入磁盘:在执行事务过程中,没执行一条 sql 语句就会产生若干条redo log
,这些日志按照产生顺序写入磁盘的,也就是使用顺序IO
log block
InnoDB存储引擎中,重做日志都是以512字节进行存储的,重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块大小为512字节
redo log 格式
由于InnoDB存储引擎的存储管理是基于页的,故其重做日志格式也是基于页的,其头部格式由三部分组成为:
- redo_log_type:重做日志的类型
- space:表空间的ID
- page_no:页的偏移量
undo log
redo 存放在重做日志文件中,与 redo 不同的是,undo 存放在数据库内部的一个特殊段中,这个段称为 undo 段,重做日志记录了事务的行为,可以很好的通过其对页进行“重做”操作,但是事务有时还是需要进行回滚操作,那么这时就需要 undo ,除了回滚操作,undo 的另外一个作用是 MVCC(多版本并发控制),即InnoDB存储引擎的MVCC的实现是由 undo 来完成的。
undo存储管理
InnoDB存储引擎有 rollback segment,每个回滚段种记录了 1024 个 undo log segment,而在每个 undo log segment 段种进行 undo 页的申请。
undo log 格式
- insert undo log:insert 操作产生的undo log ,在事务提交后就删除
- update undo log:delete 和 update 操作产生的 undo log,该undo log 可能需要提供MVCC机制,因此不能在事务提交时就删除
purge
- purge 用于最终完成 update 和 delete 操作,来支持 MVCC
- 是否可以完全删除由 purge 来判断,若该行记录已完全不被事务引用,那么就进行真正的 delete 操作
group commit
若事务为非只读事务,则每次事务提交时需要进行一次fsync操作,以此保证重做日志都已经写入磁盘,为了提高磁盘fsync的效率,当前数据库提供了 group commit 的功能,即一次fsync可以刷新确保多个事务日志被写入文件。
对于InnoDB来说,提交事务的两个阶段:
- 修改内存中事务对应的信息,并且将日志写入重做日志缓冲
- 调用fsync将确保日志都从重做日志缓冲写入磁盘
事务控制语句
- start transaction、begin:开始显示事务
- commit:提交事务
- rollback:回滚事务
- savepoint identifier:允许在事务中创建一个保存点,一个事务可以由多个保存点
- release savepoint identifier:删除一个事务保存点,当没有一个保存点,执行该条语句时会抛出一个异常
- rollback to[savepoint] identifier:这个语句与savepoint命令一起使用,可以把事务回滚到标记点,而不回滚在此标点之前的任何工作
- set transation:设置事务的隔离级别
使用事务时不好的习惯
- 在循环中提交
- 使用自动提交
- 使用自动回滚
长事务
- 执行时间较长的事务
- 对于长事务的问题,有时可以通过转换为小批量的事务进行处理,当事务发生错误时,只需要回滚一部分数据,然后接着上次完成的事务继续执行
本作品采用《CC 协议》,转载必须注明作者和本文链接