02.日志系统
bin log 备份日志
- 作用:
Server层
生成的日志,主要用于数据备份和主从复制
bin log刷盘时机:
- 事务执行过程中,先把日志写到 binlog cache(Server 层的
cache),事务提交的时候,再把 binlog cache
写到操作系统的内核缓冲区
page cache
,最后通过系统调用fsync
刷盘。
bin log 刷盘频率控制:
sync_binlog
= 0 ,表示每次提交事务都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;sync_binlog
= 1 ,表示每次提交事务都会 write,然后马上执行 fsync;sync_binlog
= N(N>1) ,表示每次提交事务都 write,但累积 N 个事务后才 fsync。
bin log 格式
binlog_format
:
- STATEMENT:(记录动作)
- 每一条
修改数据的 SQL
都会被记录到 binlog 中(相当于记录了逻辑操作,所以针对这种格式,binlog 可以称为逻辑日志),主从复制根据 SQL 语句重放 - 问题:但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
- 每一条
- ROW(默认):(记录结果)
记录行数据最终被修改成什么样
(这种格式的日志,就不能称为逻辑日志了)- 问题:但 ROW 的缺点是每行数据的变化结果都会被记录,比如执行批量
update 语句,更新多少行数据就会产生多少条记录,使
binlog 文件过大
- MIXED:
- 包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;
- 折中方案
redo log 重做日志
- 作用:
Innodb存储引擎层
生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复 - 常识:
- WAL技术 ( Write-Ahead Logging):先写日志,再写磁盘
- crash-safe:即使数据库发生异常重启,之前提交的记录都不会丢失
- redo log 记录内容:
- redo log 是
物理日志
,记录了某个数据页做了什么修改(对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了 AAA 更新)
- redo log 是
- redo log 如何确保crash-safe:
- 当系统崩溃时,即使
Buffer Pool
脏页数据没有持久化,通过已经持久化的轻量级redo log
,将所有数据恢复到最新的状态
- 当系统崩溃时,即使
redo log 刷盘时机:
log buffer
空间不足时(innodb_log_buffer_size)事务提交时
- 后台线程不停的刷(其实是兜底逻辑)
- 正常关闭服务器时(异常时)
- 发生
checkpoint
时 - 其他的一些情况
redo log的生成和写入:
- 综述:
- 事务开始执行时,可能会有多个mtr,每个mtr执行时,生成多个redo log为一组
- redo
log记录会按组为单位,先被写入到
Buffer Pool
中的redo log buffer
中,这个时候更新就算完成了 当事务提交时
,再将redo log buffer
持久化到磁盘中- 这样就不需要等待
Buffer Pool
里的脏页
数据持久化到磁盘 - 后台进程再将
Buffer Pool
中的用户数据刷盘处理
- 既然redo log 也要刷盘,为什么不直接将用户数据刷盘?
- redo log 占用的空间非常小,用户数据可能会很大
- redo log 日志是顺序IO刷盘,用户数据刷盘可能是随机IO刷盘
Mini-Transaction
(mtr)- 对底层页面中的
一次原子访问的过程
称之为一个Mini-Transaction(mtr) - 一个事务可以包含若干条语句,每一条语句其实是由若干个
mtr
组成,每一个mtr
又可以包含若干条redo
日志。 - 为什么要基于mtr做redo log记录分组:
- 在恢复时,一次mtr对应的一组
redo log
需要保证事务的原子性。要么全部成功,要么都失败。
- 在恢复时,一次mtr对应的一组
- 多条redo log记录如何分组:
- 分隔:组和组之间通过 type=
MLOG_MULTI_REC_END
的redo log进行分隔,组内只有一个redo log除外 - 通过type高位1bit,type最高位=
1
,表示是生产一条redo log记录
- 分隔:组和组之间通过 type=
redo log 记录结构
:redo log 页结构
:- 用来存储
redo
日志的页
称为block
,大小为512字节
- log block body:存储redo log 记录
- log block header、trailer:存储管理信息
- 用来存储
redo log buffer 结构
:- 如何确定buffer的写入位置:
buf_free
的全局变量,该变量是偏移位置,指明后续写入的redo
日志应该写入到log buffer
中的哪个位置 - 一个事务可以有多个mtr,每当一个
mtr
执行完成时,伴随该mtr
生成的一组redo
日志就需要被复制到redo log buffer
;多个事务的多个mtr可以交叉写入buffer;当事务提交时,需要将所有mtr对应的redo log
刷盘。
- 如何确定buffer的写入位置:
- 对底层页面中的
redo log在磁盘中的结构:
- redo日志文件组:
- 文件名称:ib_logfile_x
- 大小:每个redo log文件大小:innodb_log_file_size
默认48MB,每组文件数量 innodb_log_files_in_group 默认2
- redo log 刷盘:
- 采用循环使用的方式将redo log buffer中的数据,写到redo日志文件组里
- 使用checkpoint做偏移标记
lsn
:log sequeue number
日志序列号- 作用:记录当前系统产生的redo log的日志量的总和
- 初始值:8704,就这么规定的
- 和buf_free全局变量的关系,lsn是结构里面的字段,buf_free是程序中的全局变量
- 当buf_free为12字节时,lsn = 8704 + 12
- 一个mtr可能会有多条redo log记录,所以对应的会有lsn起始值和结束值
flushed_to_disk_lsn
:- 作用:表示redo log buffer中已经刷新到磁盘的序列号,用于记录下次写入buffer的偏移位置
- lsn和flushed_to_disk_lsn之间的差距值就是还未写入磁盘的redo log 数据,如果值相等表示已经全部写入磁盘
- 和buf_next_to_write全局变量的关系,flushed_to_disk_lsn是结构里面的字段,buf_next_to_write是程序中的全局变量
flush链表中的lsn
:oldest_modification
:某个页被加载到Buffer Pool后进行第一次修改,那么就将修改该页的mtr开始时对应的lsn值记为该脏页的oldest_modificationnewest_modification
:某个页每次被修改,都会将修改该页的mtr结束时对应的lsn更新到newest_modification- 上图解释:一次事务中,发生三次mtr
- 第一次mtr起始lsn为8716,结束为8916,过程中更新了页a
- 第二次mtr起始lsn为8916,结束为8848,过程中更新了页c、页b
- 第三次mtr起始lsn9948,结束为10000,过程中更新了页b、页d
- 第二次mtr时,
在当前事务中
因为页b已经在buffer中,所以页b不用移动,页b的o_m不变,将该mtr结束时lsn更新给页b的n_m,页d为新页,所以会插入flush链表头部
- flush链表中的脏页顺序:
- 修改发生的时间倒序,即oldest_modification
- 一个事务中被多次更新的页面不会重复插入到flush链表中,但是会多次更新newest_modification
checkpoint_lsn
:- redo log 如何做到循环使用:
- 通过checkpoint_lsn值,判断对应的脏页是否已经刷新到磁盘里,如果已经完成,则可以覆盖
- checkpoint过程:
- 步骤一:计算一下当前系统中可以被覆盖的redo日志对应的lsn值最大是多少
- 从flush链表中找出当前系统中被最早修改的脏页对应的oldest_modification(因为有序性,所以一定是flush链表末尾页对应的o_m)
- 步骤二:更新checkpoint_no(每次checkpoint加1)、checkpoint_lsn(最早的脏页o_m)、checkpoint_offset(lsn在日志文件中的offset)到redo日志文件组的管理信息中(当checkpoint_no的值是偶数时写到checkpoint1中,是奇数时写到checkpoint2中)
此时的checkpoint_lsn为页c的o_m,即8916
- 步骤一:计算一下当前系统中可以被覆盖的redo日志对应的lsn值最大是多少
- redo log 如何做到循环使用:
- 各种lsn和redo log file的关系:
- lsn:全局redo log总量
- flushed_to_disk_lsn:
redo log buffer
中待写入的lsn - checkpoint_lsn:
flush 链表
中未刷入磁盘中最早的脏页对应的lsn
- 查看系统的各种lsn:
mysql> SHOW ENGINE INNODB STATUS\G (...省略前面的许多状态) LOG --- Log sequence number 124476971 Log flushed up to 124099769 Pages flushed up to 124052503 Last checkpoint at 124052494
- 崩溃后如何恢复:
- 恢复的起始点:checkpoint_lsn
- 恢复的终点:redo log日志文件中,数据未写满512字节的那个block
- 加速恢复:
- 使用hash表
- 通过space ID + page number计算散列值(详细看redo log 日志格式),散列值相同,按照生成的先后顺序通过链表法连接。这些相同的散列值,表示对同一个page的修改的redo log记录,这样可以一次性将一个页面修复好
- 之后就可以遍历哈希表,将页面逐个修复
- 跳过已经刷新到磁盘的页
- 判断方式File Header中的FIL_PAGE_LSN(值等于newest_modification)与checkpoint_lsn比较。如果小于等于checkpoint_lsn,则表示页已经被刷到盘里,可以跳过
- 使用hash表
binlog
和redo log
区别:
- 日志格式
- 空间大小和写入方式
- 适用对象不同
- 用途不同
两阶段提交:
- 含义:将redo log 的写入步骤拆成了两个:
prepare
和commit
- 作用:确保redo log 和binlog 两份日志之间的逻辑一致
- redo log 影响主库的数据,binlog 影响从库的数据,所以 redo log 和 binlog 必须保持一致才能保证主从数据一致
两阶段提交
其实是分布式事务一致性协议,它可以保证多个逻辑操作要不全部成功,要不全部失败,不会出现半成功的状态
undo log 回滚日志
:
- 作用:
Innodb存储引擎层
生成的日志,实现了事务中
的原子性,主要用于事务回滚和 MVCC
undo log 生成过程:
- 在
事务没提交前
,MySQL会先记录更新前的数据
到 undo log 日志文件里面。操作之后就可以销毁。 - 操作不同,需要记录到undo log的内容也不同
原子性场景:
- 当事务需要回滚时(异常回滚、主动回滚),可以利用undo log来进行回滚
undo log + 一致性视图实现MVCC:
- 通过undo log 的
roll_pointer
指针 和trx_id
事务id构成一条记录的版本链
- 通过一致性视图,可以获得当前活跃事务
IDs
undo log记录的结构:
- 不同的sql生成的undo log格式不一样
- insert 操作
- 当我们向某个表中插入一条记录时,实际上需要向聚簇索引和所有的二级索引都插入一条记录。不过记录undo日志时,我们只需要考虑向聚簇索引的情况。(因为聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的undo操作)
- delete 操作
- 过程:
- 阶段一:仅仅将记录的delete_mask标识位设置为1,称为delete_mask
- 阶段二:当该删除语句所在的事务提交之后,会有专门的线程做purge操作,真正的把记录做删除掉。(把该记录从
正常记录链表
中移除,并且加入到垃圾链表
PAGE_FREE)
- 过程:
- update 操作
- 不更新主键:
- 就地更新(字段占用的存储空间大小不发生变化):
- 直接在原记录的基础上修改对应列的值
- 先删除,再新增(字段占用的存储空间大小发生变化)
- 先把这条旧的记录从聚簇索引页面中
真正删除
,然后再根据更新后列的值创建一条新的记录插入到页面中 - 优化点:空间不超过旧记录占用的空间,可以直接重用被加入到垃圾链表中的旧记录所占用的存储空间,否则需要在页面中新申请
- 先把这条旧的记录从聚簇索引页面中
- 就地更新(字段占用的存储空间大小不发生变化):
- 更新主键:
- 更新了某条记录的主键值,意味着这条记录在聚簇索引中的位置将会发生改变
- 过程:
- 将旧记录进行delete mark操作
- 根据更新后各列的值创建一条新记录,
重新定位插入的位置
,并将其插入到聚簇索引
- 不更新主键:
- insert 操作
- 查询时
- 不详细展开
undo log和数据页的关系:
- 通过roll_pointer关联
undo log 和事务的关系:
- 单个事务中的Undo页面链表:
- 一个事务可能包含多个语句,一个语句可能对若干条记录进行改动,而对每条记录进行改动前,都需要记录1条或2条的
undo日志
- 所以在一个事务执行过程中可能产生很多
undo日志
,这些日志可能一个页面放不下,需要放到多个页面中,这些页面就通过TRX_UNDO_PAGE_NODE
属性连成了链表 - 每个undo 页链表的首页是特殊的first undo
page,存储了一些额外的管理信息
Undo Log Segment Header
,记录页链表的段信息
- 一个事务可能包含多个语句,一个语句可能对若干条记录进行改动,而对每条记录进行改动前,都需要记录1条或2条的
- 单个事务中会生成哪些链表:
- undo log类型:
- 同一个
Undo页面
只能存储TRX_UNDO_INSERT
大类的undo日志TRX_UNDO_UPDATE
大类的undo日志,这样就有了insert undo 链表和update undo 链表
- 同一个
- 普通表和临时表:
- 普通表和临时表的记录改动时产生的
undo日志
要分别记录,这样就有了普通表 undo 链表
和临时表 undo 链表
- 普通表和临时表的记录改动时产生的
- undo log类型:
- 多个事务中的Undo页面链表:
- 为了尽可能提高
undo日志
的写入效率,不同事务执行过程中产生的undo日志需要被写入到不同的Undo页面链表中,这样每个事务的链表完全独立
- 为了尽可能提高
- 事务写undo log的过程:
- 一个事务在向Undo页面中写入undo日志时的方式是十分简单暴力的,就是直接往里怼,写完一条紧接着写另一条,各条undo日志之间是亲密无间的。写完一个Undo页面后,再从段里申请一个新页面,然后把这个页面插入到Undo页面链表中,继续往这个新申请的页面中写。
- 一个事务在向Undo页面中写入undo日志时的方式是十分简单暴力的,就是直接往里怼,写完一条紧接着写另一条,各条undo日志之间是亲密无间的。写完一个Undo页面后,再从段里申请一个新页面,然后把这个页面插入到Undo页面链表中,继续往这个新申请的页面中写。
重用Undo page:
- 重复使用已提交的事务的
undo page
- 条件:
- 该链表中只包含一个
Undo page
- 该
Undo page
已经使用的空间小于整个页面空间的3/4
- 该链表中只包含一个
回滚段:
- 含义:在同一时刻系统里有多个事务,此时会有多个
Undo页面
链表存在。为了更好的管理这些链表,通过Rollback Segment Header
的页面,来管理这些Undo页面
链表。在这个页面中存放了各个Undo页面
链表的first undo page
的页号
,这些Undo页面
链表的首个页面(first undo page)在回滚段内被称为undo slot
- 一个回滚段可以存放1024个
undo 页面 链表
- 回滚段存储在系统表的5号页面:
- 该页面可以存128个回滚段
本文作者:navyum
本文链接:https://www.cnblogs.com/navyum/p/18509403
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步