MySQL之7---InnoDB 和 事务流程、Crash Recovery、ACID
MySQL之7---InnoDB 和 事务流程、Crash Recovery、ACID
InnoDB术语和概念
InnoDB概述图
表数据
- 磁盘(.ibd)每表文件表空间
- 段(segment)
- 区(extents)
- 页(pages)
- 内存(innodb_buffer_pool)缓冲池
- innodb_buffer_pool_size
- innodb_buffer_pool_instances
重做日志 (Redo Log)
-
磁盘(ib_logfile0~N)重做日志
- innodb_log_file_size
- innodb_log_files_in_group
-
内存(innodb_log_buffer)日志缓冲区
- innodb_log_buffer_size
-
预先分配至少2个日志文件,第一个文件开头和最后一个文件结尾进行首尾相连以循环的方式重复使用。
-
用于在必要时(如:崩溃恢复时)可以使用Redo Log中的数据来重新应用到InnoDB数据文件中,使得InnoDB能够恢复到一致性状态。
-
日志先行(WAL:Write Ahead Log),Redo Log 优先于数据页写到磁盘。
-
对数据表做修改时,每个数据记录的修改都会写入Redo Log Buffer中(记录重做日志)。当一个页面的修改操作完成时,首先Redo Log Buffer将执行flush同步到Redo Log文件,之后才可能将实际更改数据写入磁盘(buffer pool中的脏页刷新到数据文件)。
-
批量刷写,如果系统参数innodb_flush_log_at_trx_commit设置为1,则每个事务提交时也会将Redo Log Buffer同步到磁盘文件中。
-
Redo Log 日志组结构
撤消日志(Undo Log)
-
用于撤消(或还原)对InnoDB中存储的数据的变更及回滚事务,也用于实现多版本控制(mvcc),通过构建一致性视图(read view)实现对数据库的一致性读。
-
对数据库的每一次更改,Undo Log都会保存之前版本的数据和回滚指针(每个聚簇(PK)索引记录都有的一个指向该修改记录之前版本数据的指针),另外,每个Undo Log的变更也必须记录到Redo Log中。
-
Undo Log在共享表空间的基本结构
- 在共享表空间的第6个页存放了InnoDB的事务系统信息,其中也包含了Undo Log的一些系统信息
- 事务系统信息页(TRX_SYS)结构 ,最多可以创建128个回滚段(MySQL 8.x版本可以有多个文件)
- 每个回滚段中都需要有一个单独的page 系统选择头部(SYS_RSEG_HEADER)来维护其拥有的 undo solt(通常是每个回滚段中的第一个页),每个回滚段有1024个撤消段槽(Undo segment slot),每个撤消段槽指针都指向每个回滚段中的第一个UNDO_lOG页中的回滚段头(Rollback Segment Header)
- Undo Log的数据存储在系统表空间的UNDO_LOG页中
- UNDO_LOG页中UNDO段头部、UNDO页头部、UNDO_LOG记录
日志序列号(LSN)
-
Log Sequence Number(LSN)
-
一个64位无符号整数,表示Redo Log系统中的时间点,也是事务写入Redo Log的字节总量,从日志初始化(数据库初始化安装)时间点开始单调递增计数
-
LSN存放在Redo Log和每个数据页,数据库每次启动,都会比较磁盘数据页和 Redo Log 的LSN,必须两者一致数据库才能正常启动,如果不一致就会触发CR(崩溃恢复),最终保证一致
-
在数据恢复时,通过LSN做比较运算,可以判断出每个数据页是否需要进行恢复操作
-
可以在存储引擎状态信息中获取
show engine innodb status\G
-
使用LSN常见对象
- Redo buffer:Log sequence number 105160141
- Redo Log:Log flushed up to 105160141
- 数据页:Last checkpoint at 105160141
检查点(Checkpoint)
-
Checkpoint 指将脏页刷写到磁盘的动作
-
脏页(Dirty Page):发生了修改,没写入到磁盘之前的内存页称之为脏页.
-
Checkpoint LSN 是一个由LSN值表示的整型值,一个时间点,在Checkpoint LSN之前的每个数据页(buffer pool中的脏页)的更改都已经落盘(刷新到数据文件中),Checkpoint 完成后,在Checkpoint LSN之前的Redo Log就不再需要了
-
Checkpoint 用于解决:全量Redo Log恢复时间太长、buffer pool中的空闲页不够用时将脏页刷新到磁盘数据文件、Redo Log空间不够用时将脏页刷新到磁盘数据文件等问题
-
Checkpoint 方式有两种:Sharp Checkpoint和Fuzzy Checkpoint(又可根据不同的场景细分)
- Sharp Checkpoint:将所有的脏页刷回磁盘,数据库实例关闭时系统参数innodb_fast_shutdown设置为0,才需要把所有的脏页都刷回磁盘,刷脏时系统hang住
- Fuzzy Checkpoint:持续的每次只刷新一部分脏页到磁盘,数据库正常运行过程中都是使用这种方式刷脏,在InnoDB内部还可细分为如下几种:
- Master线程每秒/每十秒固定执行Checkpoint
- LRU list中空闲页不够时,触发Checkpoint从LRU list刷新脏页以释放足够的空闲页
- Redo Log空间不够时,触发Checkpoint从Flush list刷新脏页,Checkpoint执行完成之后,在这个位置之前的Redo Log不再需要(即,可以循环覆盖使用)
- 脏页太多达到脏页比例阀值(系统参数innodb_max_dirty_pages_pct和innodb_max_dirty_pages_pct_lwm控制脏页比例阀值),触发Checkpoint
回滚指针(ROLL_PTR)
- Rollback Pointer(ROLL_PTR)
- 一个由rollback segment number、page number和page offset组成的指针,指向Undo Log中包含之前版本数据的具体Undo Log日志记录,存储在数据页头部,大小为
7
个字节, - 可用于为任何数据记录回退到一个历史版本记录、可用于mvcc中重建旧版本记录、可用于事务回滚
- ROLLBACK 时,会使用 undo 日志回滚已修改的数据。回滚指针指向此次事务的回滚位置点,用来找到undo日志信息。
事务ID号(TRX_ID)
- Transaction ID(TRX_ID)
- 表示事务开始点的一个增量增加的64位无符号整数,存储在数据页头部,大小为
6
个字节,最大事务号写入系统表空间的TRX_SYS页,伴随着整个事务生命周期.
事务的工作流程原理
① 事务start(事务首次开启)
- 为这个事务分配事务ID(TRX_ID),该事务ID可能被写入系统表空间的TRX_SYS页面中的最大的事务ID字段(Transaction ID)
- 如果系统表空间的TRX_SYS页面中的最大的事务ID字段被更新,则该更新会被记录到Redo Log中
- 根据分配的TRX_ID创建read view
② Update(每次只修改一行记录)
- 分配
Undo Log
日志空间 - 拷贝该记录修改之前的值到
Undo Log
中 - 将
Undo Log
的修改记录写入Redo Log
中 - 在
buffer pool
中修改数据页,回滚段指针指向Undo Log
中该记录之前的版本 - 将该记录对应的数据页变更部分写入
Undo Log
中 buffer pool
中该记录修改之后的数据页被标记为"脏页"(需要刷新到磁盘的数据页)
此时其他事务:
- 一旦记录被修改,即使没有提交,其他事务也可能会看到被修改后的记录,这依赖于他们的事务隔离级别而定
- 如果是RU隔离级别,则使用索引页读取最新版本记录
- 如果是RU隔离级别,则查找记录的最新提交版本
- 如果是RR隔离级别,则查找与其read view相对应的记录版本
- 任何需要使用索引页来读取比最新的版本记录旧的版本记录时,都必须使用Undo Log来重建之前的版本记录
③ 事务提交(显式或隐式)
- 事务对应的Undo Log页被设置为"purge"(意味着当这个Undo Log页不再被任何其他事务引用时可以将其清除)
- 将Undo Log的修改记录写入Redo Log中
- Redo Log Buffer刷新到磁盘(是否刷盘取决于系统变量innodb_flush_log_at_trx_commit的设置)
- Redo Log 打上 commit 标记
④ 后台线程刷脏
触发方式:
-
定期执行Checkpoint
-
确保比Checkpoint 点更旧(比Checkpoint LSN小)的脏页已刷新到表空间文件,如果存在有比Checkpoint LSN大的脏页,则立即刷新脏页到数据文件中。说白了Checkpoint机制主要作用就是用于刷新脏页
-
把Checkpoint LSN写到Redo Log Header中 (从这个Checkpoint LSN开始,之前的Redo Log记录不再需要)
-
-
后台线程Purge
-
后台线程连续不断地根据需要定期执行Purge,包括Undo Log和历史链表
-
查找每个回滚段中不再需要的最旧的Undo Log
- 实际上是从索引中删除任何带有删除标记的记录
- 释放Undo Log页
- 修剪history lists
执行流程:
- 查找最旧的“脏”页面(修改时间最早的页面)并将其添加到flush batch中
- 确保在flush batch中中最新的LSN号已经写入到了Redo Log中且已经落盘
- 如果开启了双写,则先将脏页刷新到双写缓冲区(并等待同步)
- 将每个脏页从buffer pool中写入最终目的地:表空间文件中的
PS:对于后台线程刷脏部分,执行刷新脏页时,与该脏页的事务是否提交无关,只需要确保该页对应LSN号的Redo Log记录落盘,而不会去判断事务的状态是否是提交还是未提交状态,因为,数据页结构中并没有地方单独记录事务的状态(即,无法判断事务是否提交),只是在每行数据中有记录事务号、回滚段指针(所以一个页中也可能包含多个事务的修改记录)。当需要对某个事务进行回滚时,重新从表空间中读取这个未提交的脏页,使用undo log中的反向数据进行反向修改,然后再重新刷脏。
Creash Recovery
触发条件
- 实例崩溃之后重启
- 使用一个备份还原(如:LVM 快照、xtrabackup备份)后
- 在“快速”(innodb_fast_shutdown不为0值关闭实例)关闭实例后重新启动
① 检测实例是不是干净地关闭的
- 打开Redo Logs和系统表空间文件(ibdataN)
- 读取并从中找到最大的Checkpoint LSN
- 从最近的Checkpoint 开始往后扫描Redo Log
- 如果能够找到Redo Log记录,说明还有数据页的更改没有刷新到数据文件上,启动Crash Recovery,使用Redo Log来恢复数据的一致性
② 前滚恢复实例
原理:使用所有独立表空间的表名和表空间ID创建一个名称到ID的映射
- 打开datadir下的所有.ibd文件
- 在这些表空间文件的offset 0的页(FSP_HDR页)头读取其表空间ID(FSP_HDR页中FSP Header的前四个字节记录着表空间ID)
- 将表空间ID与表名建立映射
PS:
- page offset 0(FSP_HDR页)页结构
- FSP_HDR页中FSP Header的前四个字节记录着表空间ID
③ 损坏页修复
- 检查双写缓冲区中的所有128个页,读取表空间中的每个“目标”页
- 如果页头和页尾的LSN不匹配或页面校验和无效,则使用双写缓冲区(Double Write Buffer)中的页进行还原
- 如果该页在双写缓冲区中的版本也被破坏,server crash
④ 回滚未提交事务
- 事务系统初始化(回滚段初始化)
- 从最近的
Checkpoint
往后扫描到的Redo Log
记录将被应用到各个数据文件中 - 从
Undo Log
中恢复处于'ACTIVE
'状态的事务,重新生成Read View
- 使用
Undo Log
回滚未提交的'ACTIVE
'状态的事务 - 处于
PREPARE
状态的事务,如果打开了binlog
且在binlog
有找到对应事务的日志则重新提交,否则回滚
InnoDB和ACID模型
原子性 A
ACID模型 的原子性方面主要涉及InnoDB
事务。相关的MySQL功能包括:
一致性 C
ACID模型 的一致性方面主要涉及内部InnoDB
处理,以防止数据崩溃。相关的MySQL功能包括:
InnoDB
doublewrite buffer :默认存储在ibdataN中,数据页写入过程中,建立的副本。InnoDB
崩溃恢复 :数据库意外宕机时,通过redo前滚和undo回滚,保证数据的最终一致。
隔离性 I
ACID模型 的隔离性方面主要涉及InnoDB事务
,尤其是适用于每个事务的隔离级别。相关的MySQL功能包括:
- 自动提交设置。
SET ISOLATION LEVEL
隔离级别设置声明。InnoDB
锁 。可以通过INFORMATION_SCHEMA
表查看详细信息 。
持久性 D
ACID模型 的持久性方面涉及与特定硬件配置交互的MySQL软件功能。相关的MySQL功能包括:
InnoDB doublewrite buffer
,由innodb_doublewrite
配置选项打开和关闭 。- 配置选项
innodb_flush_log_at_trx_commit
。 - 配置选项
sync_binlog
。 - 配置选项
innodb_file_per_table
。 - 存储设备(例如磁盘驱动器,SSD或RAID阵列)中的写缓冲区。
- 存储设备中由电池支持的缓存。
- 用来运行MySQL的操作系统,特别是它对
fsync()
系统调用的支持。 - 不间断电源(UPS)保护运行MySQL服务器并存储MySQL数据的所有计算机服务器和存储设备的电源。
- 备份策略,例如备份的频率和类型以及备份保留期。
- 对于分布式或托管数据应用程序,MySQL服务器的硬件所位于的数据中心的特定特性,以及数据中心之间的网络连接。
前滚(redo log)和回滚(undo log)
InnoDB核心特性:自动故障恢复。先前滚再回滚,先应用redo再应用undo。
Redo Log 应用在事务ACID过程中,实现的是“D”持久化的作用,对于AC也有相应的作用。
Undo Log 应用在事务ACID过程中,实现的是“A” 原子性的作用,对于CI也有相应的作用。
-
用户发起事务语句(
begin;DML;commit;
) -
begin;
,立即分配一个TXID=tx_01
。 -
DML;
,将需要修改的磁盘数据页(PageNo=10,数据,LSN=3515),加载到内存(buffer_pool
)缓冲区。 -
申请 undo log 日志空间,保存数据到回滚日志(逆向操作的逻辑日志)
-
记录 undo log 操作到
redo log
。 -
DBWR线程,在内存中,修改数据页头部(记录TRX_ID+ROLL_PTR),更新数据页(PageNo=10,变化的数据,LSN=3515+变化的字节)形成脏页。
-
LOGBWR日志写线程,将 数据页的变化 + LSN=3515+变化的字节 + TXID 存储到
redo buffer
。如果此时系统宕机,内存数据会全部丢失:
- MySQL再次启动时,触发CR(自动故障恢复),检测
buffer_pool
和磁盘数据页的LSN是否一致。 log buffer
LSN = 数据页 LSN :不需要进行前滚和回滚。undolog直接被标记为可覆盖状态。MySQL正常启动。
- MySQL再次启动时,触发CR(自动故障恢复),检测
-
commit;
,LOGBWR线程,将redo buffer
写入磁盘中的redolog
日志文件(ib_logfileN)。基于批量刷写redo机制:在事务commit时,会顺便将
redo buffer
中其他未提交的redolog
也一并刷到磁盘。日志记录时,使用COMMIT标记区分状态。基于WAL(日志先行)机制:在日志完全写入磁盘后,给此日志打上commit标记,commit命令才算执行成功。
-
此时 binlog 完全写入磁盘,但脏页没有写入磁盘,由其他后台线程检测刷脏(
CheckPoint
)如果此时系统宕机,内存数据会全部丢失:
- MySQL再次启动时,触发CR(自动故障恢复),检测
buffer_pool
和磁盘数据页的LSN是否一致。 log buffer
LSN > 数据页 LSN :加载ib_logfileN到log buffer
,磁盘数据页到buffer_pool
。使用redolog
重构脏页(前滚)。- 如果确认此次事务已提交(有commit标签),立即触发CKPT,将脏页刷写到磁盘上。
- 如果确认此次事务未提交(没有commit标签),立即触发回滚操作(等同执行
rollback;
),根据DB_TRX_ID + DB_ROLL_PTR,找到undolog,实现回滚。
- 此时磁盘数据页LSN和
redolog
LSN 一至。MySQL正常启动。
- MySQL再次启动时,触发CR(自动故障恢复),检测
Flush磁盘参数
innodb_flush_log_at_trx_commit(双一标准之一)
innodb_flush_log_at_trx_commit 作用:控制innodb将log buffer中的数据,写入日志文件并flush磁盘的时间点。
select @@innodb_flush_log_at_trx_commit;
参数说明
0:每秒写入一次日志并将其刷新到磁盘。未刷新日志的事务可能会在崩溃中丢失。
1:每次事务提交时,日志都会写入并刷新到磁盘。完全符合ACID,默认设置。
2:在每次事务提交后写入日志,并每秒刷新一次到磁盘。未刷新日志的事务可能会在崩溃中丢失。
Innodb_flush_method
Innodb_flush_method 作用:控制的是,log buffer 和data buffer,刷写磁盘的时候是否经过文件系统缓存。
select @@innodb_flush_method;
参数说明
fsync或0: 刷新日志和数据缓冲区到磁盘,都走OS buffer
O_DSYNC或1: 刷新日志缓冲区到磁盘,不走OS buffer
O_DIRECT或4: 刷新数据缓冲区到磁盘,不走OS buffer
官方参数说明
fsync或0: InnoDB使用fsync()数据和日志文件。默认设置。
O_DSYNC或1: InnoDB使用O_SYNC打开和刷新日志文件,使用fsync()刷新数据文件。
O_DIRECT或4: InnoDB使用O_DIRECT打开数据文件,使用fsync()刷新数据和日志文件。
O_DIRECT_NO_FSYNC: InnoDB在刷新I/O期间使用O_DIRECT,但在每次写操作后跳过fsync()。
使用建议
# 最高安全模式
innodb_flush_log_at_trx_commit=1
Innodb_flush_method=O_DIRECT
# 最高性能模式
innodb_flush_log_at_trx_commit=0
Innodb_flush_method=fsync