MySQL 三大日志
本文将介绍 MySQL 中的 binlog,二进制日志。
一、binlog
1. binlog 是什么?
- binlog 是 服务层 的日志
- binlog 是 binary log 的缩写,即二进制日志
- binlog 中记载了数据库的变化,包括:新建数据库、新建表、改动表结构、改变数据等
2. binlog 的使用场景
复制:
MySQL 主从模式依赖于 binlog,从库会读取并重做 binlog 中的内容,达到与主库数据一致的目的
恢复:
在现实场景中,数据库中的数据可能被有意或无意地删除,又因为随时做全量备份是不现实的,我们可能会不可避免地损失部分数据。
这时候,binlog 便可以排上用场,只需要恢复全量备份,再执行全量备份至删除数据前的所有 binlog,就可以将数据库恢复至删除数据前的状态
审计:
因为 binlog 中包含数据库的每次改动,因此可以利用它进行 SQL 审计
3. binlog 的格式
基于语句:记录 SQL 语句
可能会造成主从数据库的数据不一致。
例如两个数据库的自增值不同,导致插入的记录的主键不同
基于行:记录语句执行中每一条记录更新前后的值
基于行的 binlog 有着占用空间过大的缺点
假设用 delete 语句删除 10 万行数据,则基于语句的 binlog 只需要记录一个 SQL 语句,而基于行的 binlog 需要记录 10 万条记录,这样做会占用极大的空间,并且也会耗费 IO 资源,影响执行速度
mixed 格式:基于语句和基于行的结合,MySQL 会判断语句是否可能造成主从数据库不一致,如果会,用基于语句的格式,否则用基于行的格式
4. binlog 的写入机制
写入逻辑:
- 事务执行过程中,把日志写到 binlog cache 中
- 事务提交时,把 binlog cache 写到 binlog 中
binlog cache 逻辑:
系统分配了一片内存空间,每个线程拥有一块独立的 binlog cache,但是都要最终写入到同一个 binlog 文件中。
write、fsync 逻辑:
write 和 fsync 的时机由参数 sync_binlog 控制:
- 为 0 时,只 write 不 fsync
- 为 1 时,每次都 write 并 fsync
- 为 N 时,累计 N 个事务进行一次 fsync
一般情况下,将其设置为 100 ~ 1000 之间的某个数值。
二、redo log
1. redo log 是什么?
redo log 是 InnoDB 特有的
redo log 又被称为重做日志
假设系统崩溃时有被改动的 “数据页” 未刷回磁盘,改动便会丢失。可以根据 redo log “重做” 操作以恢复改动,这便是 redo log 名字的由来。
redo log 是持久性的保证
事务有 “持久性” 这一特征,其执行结果应该被持久存储。写入操作会首先改动缓冲池中的数据,在适当的时候才会刷回磁盘,存在崩溃导致执行结果丢失的可能。通过 redo log,将事务的改动记录,从而可以保证持久性。
redo log 记录事务对某个数据页的某个地方做了某些修改
例如:
1
将第 0 号表空间的 100 号页面的偏移量为 1000 处的值更新为 2
每次事务提交时,InnoDB 会:
- 记录 redo log,刷新至磁盘
- 更新内存中的数据,稍后刷新至磁盘
2. 先刷新 redo log 的好处
相比于将数据页刷新磁盘,先将 redo log 刷新到磁盘中的好处有:
改动可能涉及多个数据页,数据页刷回时需要将整个页面一起刷回,消耗比较大;
redo log 占用的空间更小,刷新消耗更小
改动可能涉及多个数据页,它们可能分布于不同位置,随机 IO 会影响刷新速度;
redo log 可以被顺序写入磁盘中
3. redo log 格式
各个部分的详细释义如下:
- type:该条 redo log 的类型
- space ID:表空间 ID
- page number:页号
- data:该条日志的具体内容
4. redo log 的写入机制
redo log buffer 中的内容不需要每次写入都持久化到磁盘,只需要保证事务提交时持久化。
如果在事务执行过程中宕机,事务未提交,此时日志丢失反而是正确的
redo log 的写入经过以下几个流程:
写入到 redo log buffer
刷回到 redo log 文件中,刷回时机有:
- 事务提交时,redo log 会被刷回磁盘
- InnoDB 有一个后台线程,它会每隔一秒将 redo log buffer 中的内容写入到 redo log 文件
- 当 redo log buffer 占用的空间达到 innodb_log_buffer_size 的一半时,后台线程会主动 write(但不会 fsync)
- 如果同时有其它事务进行了提交,则它会 “顺便” 将这一事务的 redo log buffer 中的内容写入到 redo log 文件
三、binlog 与 redo log
1. 区别
binlog 是 MySQL 的服务层实现的;
redo log 是 InnoDB 特有的
binlog 是逻辑日志,记录的内容是 “执行了某条 SQL 语句 / 某一行的数据进行了某些改动”;
redo log 是物理日志,记录的内容是 “某个数据页的某个地方做了某些修改”
binlog 将日志写入文件中,可以 “无限写入”;
redo log 空间固定且循环写入
2. 关联
binlog 与 redo log 通过共有的数据字段 XID 关联。
3. 为什么要有 redo log?
binlog 日志会记录数据库的变化,在 MySQL 的设计中,理想的数据修改流程应该是这样的:
- 修改数据
- 在事务提交时记录 binlog
但是,为保证性能,InnoDB 设计了缓冲池,具体见 MySQL 缓冲池。在这样的设计下,如果事务已提交且数据未同步至磁盘,此时发生崩溃重启,数据库中的数据和 binlog 记录的数据将会发生不一致。
因此,InnoDB 额外增加了 redo log,通过它来在崩溃重启后恢复数据。
4. 两阶段提交
(1) 语句执行过程中的 binlog 和 redo log
MySQL 在执行简单 update 时:
- 执行器调用存储引擎完成操作,存储引擎
- 直接操作缓冲池中的数据页,做修改
- 将操作记录存储到 redo log 中,此时 redo log 处于 prepare 状态
- 执行器生成 binlog,将 binlog 写入磁盘
- 执行器调用存储的提交事务接口,存储引擎会将 redo log 改为 commit 状态,并将 redo log 刷回磁盘
(2) 为什么需要两阶段提交?
为了让两份日志之间的逻辑保持一致。
以反证法证明,
- 假设先写 redo log 后写 binlog:如果在 redo log 写完且 binlog 未写完时崩溃,redo log 能够保证数据库的状态最新,而 binlog 中缺失语句
- 假设先写 binlog 后写 redo log:如果在 binlog 写完且 redo log 未写完时崩溃,redo log 无法保证数据库状态最新,而 binlog 中有最新数据的修改记录
因此,应该使用两阶段提交。
四、undo log
1. 什么是 undo log?
- undo log 是 InnoDB 特有的
- undo log 会将数据因修改而产生的各个版本组合成链表
2. 版本链
事务对某一行记录的多次修改会产生多个版本,这些版本在数据库中如何存储和寻找呢?
InnoDB 通过聚簇索引记录保存了记录的 “最新版本”,可以通过主键在其中找到记录的 “最新版本”
InnoDB 通过 undo 日志保存了记录的 “旧版本”,可以通过 “最新版本” 的 roll_pointer 找到由 “旧版本” 组成的 “旧版本链”
“最新版本” 与 “旧版本链” 一起,共同构成了 “版本链”:
需要注意的是:
- 数据的最新版本存储在数据页中;undo log 存储在类型为 FIL_PAGE_UNDO_LOG 的页面中
- 可以通过数据行的 roll_pointer 获取到对应的 undo log 的地址
3. undo log 什么时候删除?
undo log 不会始终保留,否则链表越来越长将极大占据空间。
当系统中没有比 undo log 更早的 ReadView 时,undo log 不再被需要,系统会自行移除。
4. 为什么不建议用长事务?
长事务意味着其 ReadView 将会持久存在,则 InnoDB 需要为其维护 “很老” 的 ReadView,记录的版本链也会变得很长,这将大量占用存储空间。
5. 作用:保证原子性
事务的 “原子性” 表示事务是最小的执行单位,不可再分,要么执行,要么不执行。假如事务在执行到一半时发生问题导致无法继续执行,这种情况下事务被认为是 “失败的”,应该 回滚 它已进行的操作。
使用 undo log 可以在事务失败后回滚。
6. 作用:保证隔离性
undo log 可以服务于 MVCC,保证事务的 “隔离性”。
参考
- MySQL 技术内幕
- MySQL 实战 45 讲
- MySQL 是怎样运行的:从根儿上理解 MySQL