WAL机制
Write-Ahead Logging,预写日志系统即当有数据更新请求的时候,先写日志,再改内存,等“有空”的时候再落磁盘(刷脏页)。WAL机制的好处,因为写日志是磁盘顺序IO,而直接写磁盘是随机IO,性能较差。
binlog
MySQL server层自己的归档日志叫做binlog (binary log)。binlog会记录所有逻辑操作,采用“追加写”的方式,log不会被覆盖。
binlog的三种格式
binlog有三种格式(binlog_format),一种叫statement, 一种叫row, 第三种叫mixed, 是前两种的混合。举个栗子🌰:
delete from t where code<=2 and create_time <= \'2022-2-18\' limit 1
如果binlog是statement的形式,binlog将记录SQL原文:
(ps 但是这条语句会产生一个warning,因为binlog设置为statement格式,语句有limit可能是unsafe的, 至于原因这里暂时不讨论,可以看链接)
如果binlog是row的形式,binlog不是记录SQL的原文而是替换成了两个event,table_map和delete_rows:
binlog的使用场景
- 主从复制
- 数据恢复
如何将数据库恢复到之前的某个时刻?
找到这个时刻之前最近的一次全量备份,再从备份的时间点开始把binlog依次取出来,重放到需要恢复的那个时刻。
binlog的落盘时机
根据参数sync_binlog来控制:
0 | 不控制binlog刷新,由文件系统控制它缓存落盘,风险较大,一旦crash, 会有binlog丢失的问题 |
1 | 每次事务提交都会刷盘到磁盘,最安全但是性能最差。 |
N | 每N次事务提交才会刷盘到磁盘 |
redo log
上面说binlog记录的是所有逻辑操作,且是server层面实现的,而redo log 记录的是数据页中的真实二进制数据,是在InnoDB存储引擎层面实现的。
redo log 包含两部分,一个是日志缓冲区(redo log buffer),一个是磁盘上的日志文件(redo log file)。InnoDB的redo log是固定大小,配置为一组4个文件,每个文件1GB,机制是循环写。write pos记录当前从哪里开始写,写之后就向后移动,check point记录可以写到哪个位置。从write pos到check point的区间(绿色部分)就是可以写日志的区域,如下图:
既然叫check point,那何时去check呢?(=什么时候刷脏页/flush?=什么时候算WAL中提到的“有空”)
脏页的落盘时机
- 第一直觉上来说,如果redo log写满了,也就是说write pos追上check point了,那必须要刷脏页,把check point往后推,否则系统就无法处理更新了。
- 第二个就是写内存的时候,发现内存不够用了,需要释放掉一些内存。在存储部分提到过,内存里的要么是空页,要么是干净页,要么是脏页。如果是干净页,就可以直接释放,如果是脏页就必须要先落盘flush
- “有空”自然是说系统较为空闲的时候,因为WAL机制本身就是为了提高系统的性能,减少随机IO带来的性能损耗。如果系统负载很小,完全handle得过来,那就刷下脏页吧
- MySQL需要关闭以前。那自然是要把数据落到磁盘上。
刷脏页的能力
可以通过innodb_io_capacity参数告诉InnoDB这个磁盘的能力,一般是设置为磁盘的IOPS(Input/Output Per Second)。InnoDB会根据这个参数和当前内存的脏页比例以及redo log的落盘四度决定刷脏页的速度。
除此之外,刷脏页还有个特点就是“连坐”。如果当前需要被刷的这个脏页的邻居页也是脏页,那也会被一起刷到磁盘。可以通过设置innodb_flush_neighbors参数为0来避免。
redo log的落盘时机
前面说的刷脏页,其实是把内存的最新数据更新到磁盘上,而上面也说了redo log其实也有缓存和磁盘文件,那redo log什么时候把缓存给落盘呢?
首先,我们要知道计算机操作系统的用户空间的缓冲区数据,要经过内核空间,由系统调用fsync()写到磁盘上,如下图:
可以通过innodb_flush_log_at_trx_commit参数来配置这个redo log的落盘时机:
0(延迟写) | 事务提交时,不会将 redo log buffer中的日志写入os buffer; 而是每秒写入os buffer并调用fsync()写入redo log file中。也就是说当系统崩溃,会丢失1s的数据 |
1(实时写,实时刷) | 事务提交时,都会将redo log buffer中的日志写入os buffer并调用fsync()刷到redo log file中。这种方式不会丢失任何数据,但是IO性能相对来说比较差 |
2(实时写,延迟刷) | 事务提交时,都会写入os buffer,但是每秒调用fsync()将os buffer中的日志写入redo log file |
redo log的使用场景
InnoDB通过redo log来实现事务,同时保证数据库即使发生异常重启,之前提交的记录都不会丢失,没有提交的事务数据自动回滚。这个能力就叫做crash-safe。
redo log & binlog
记录的内容 | 物理日志,记录的是对XX表空间的XX数据页XX偏移量的地方做了XX更新, 恢复速度快 | 逻辑日志,如真实的SQL语句,如对XX表条件为XX的数据做了什么修改,需要逐条执行,恢复速度慢 |
记录的方式 | 循环写 | 追加写 |
用途 | 重做数据页 | 主从复制,数据备份 |
层级 | innodb存储引擎提供 | mysql server层提供 |
为什么要两个日志,一个不行吗?
redo log是InnoDB提供的,MySQL很早之前还没有InnoDB这个东西,都是用binlog来归档的。但是单独的binlog不能提供crash-safe的能力。
两阶段提交
一条更新语句,到底是怎样执行的呢?例如update t set number=number+1 where id=1
两阶段提交即将写入redo log这个操作,拆成了写prepare和写commit。由此可以保证redo log和binlog的逻辑一致性,避免主从复制时产生数据不一致的问题。
试想先写redo log再写binlog或者先写binlog再写redo log会有什么问题:
- 先写redo log, 此时MySQL(master)崩溃(没来得及刷脏页),那么binlog里面这条数据还是原来的。MySQL(master)重启,会根据redo log来重放,那么此时master的数据是更新过后的,而从节点通过binlog去做复制的时候,这条数据就是老的。主比从多了一次数据更新。
- 先写bin log, 此时MySQL(master)崩溃,那么redo log里面这条数据是旧的。MySQL(master)重启,根据redo log来重放,那么此时master的数据是原来的,而从节点通过binlog去做复制的时候,这条数据就是新的。从比主多了一次数据更新。
但两阶段提交就不会有这个问题:
- 如果写入了redo log - prepare, MySQL崩溃,此时binlog啥也没有。MySQL重启,根据redo log重放的时候发现redo log是prepare但是binlog啥也没有,那就回滚。从节点根据binlog复制,不会有这条记录;master回滚了,也不会有这条记录。
- 如果写入了redo log -prepare, 写入了binlog, MySQL崩溃,此时binlog是有数据的。MySQL重启,根据redo log重放的时候发现redo log是prepare但是binlog有对应的记录,那就提交。从节点根据binlog复制,会有这条记录;master提交了,也会有这条记录
- 如果写入了redo log-prepare,写入了binlog, 写入了redo log-commit过后,MySQL崩溃了。MySQL重启,就按部就班重放redo log就行了,啥也不用做。
来源:https://www.cnblogs.com/rachel-aoao/p/mysql_log.html
图文来源于网络,如有侵权请联系删除。