MySQL -- 数据可靠性
binlog的写入机制
- 事务在执行过程中,先把日志写到
binlog cache
,事务提交时,再把binlog cache
写到binlog file
- 一个事务的binlog是不能被拆开的,不论事务多大,也要确保一次性写入
- 系统会给每个线程分配一块内存
binlog cache
,由参数binlog_cache_size
控制- 如果超过了
binlog_cache_size
,需要暂存到磁盘
- 如果超过了
- 事务提交时,执行器把
binlog cache
里面的完整事务写入到binlog file
,并清空binlog cache
1 | -- 2097152 Bytes = 2 MB |
写入过程
- 每个线程都有自己的
binlog cache
,但共用一份binlog file
write
:把日志写入到文件系统的page cache,但并没有将数据持久化到磁盘,速度比较快fsync
:将数据持久化到磁盘,fsync
才会占用磁盘的IOPS
sync_binlog
1 | mysql> SHOW VARIABLES LIKE '%sync_binlog%'; |
sync_binlog=0
,每次提交事务都只write
,不fsync
sync_binlog=1
,每次提交事务都会执行fsync
sync_binlog=N
,每次提交事务都会write
,但累计N个事务后才fsync
- 一般为
(100 ~ 1,000)
,可以提高性能 - 如果主机断电,会丢失最近的N个事务的binlog
- 一般为
redolog的写入机制
- 事务在执行过程中,生成的
redolog
需要先写到redolog buffer
redolog buffer
里面的内容,并不需要每次生成后都直接持久化到磁盘- 如果事务执行期间,MySQL异常重启,那么这部分日志丢失了
- 由于事务没有提交,所以这时的日志丢失不会有什么影响
- 在事务还未提交时,
redolog buffer
中的部分日志也是有可能持久化到磁盘的
redolog的状态
- 红色部分:存在于
redolog buffer
中,物理上存在于MySQL进程的内存 - 黄色部分:写到磁盘(
write
),但没有持久化(fsync
),物理上存在于文件系统的page cache - 绿色部分:持久化到磁盘,物理上存在于
hard disk
- 日志写到
redolog buffer
是很快的,write
到FS page cache
也比较快,但fsync
到磁盘的速度就会慢很多
redolog的写入策略
事务提交
1 | mysql> show variables like '%innodb_flush_log_at_trx_commit%'; |
innodb_flush_log_at_trx_commit=0
- 每次事务提交时都只是将
redolog
写入到redolog buffer
(红色部分) redolog
只存在内存中,MySQL本身异常重启也会丢失数据,风险太大
- 每次事务提交时都只是将
innodb_flush_log_at_trx_commit=1
- 每次事务提交时都将
redolog
持久化到磁盘(绿色部分) - 两阶段提交:
redolog prepare
->写binlog
->redolog commit
redolog prepare
需要持久化一次,因为崩溃恢复依赖于prepare的redolog
和binlog
redolog commit
就不需要持久化(fsync
)了,只需要write
到FS page cache
即可- 双1配置:一个事务完整提交前,需要2次刷盘:
redolog prepare
+binlog
- 优化:组提交
- 每次事务提交时都将
innodb_flush_log_at_trx_commit=2
- 每次事务提交时都将
redolog
写入到FS page cache
(黄色部分)
- 每次事务提交时都将
后台刷新
- 后台线程:每隔1秒,就会将
redolog buffer
中的日志,调用write
写入到FS page cache
,再调用fsync
持久化到磁盘 - 事务执行期间的
redolog
是直接写到redolog buffer
,这些redolog
也会被后台线程一起持久化到磁盘- 即一个未提交的事务的
redolog
也是有可能已经持久化到磁盘的
- 即一个未提交的事务的
事务未提交
1 | -- 16777216 Bytes = 16 MB |
- 当
redolog buffer
占用的空间即将达到innodb_log_buffer_size
的一半时,后台线程会主动写盘- 由于事务尚未提交,因此这个写盘动作是在
write
,不会调用fsync
,停留在FS page cache
- 由于事务尚未提交,因此这个写盘动作是在
- 在并行事务提交时,顺带将未提交事务的
redolog buffer
持久化到磁盘- 事务A执行到一半,有部分
redolog
在redolog buffer
- 事务B提交,且
innodb_flush_log_at_trx_commit=1
,事务B要把redolog buffer
里面的日志全部持久化到磁盘 - 这时会带上事务A在
redolog buffer
里的日志一起持久化到磁盘
- 事务A执行到一半,有部分
组提交
LSN
LSN
:log sequence numberLSN
是单调递增的,对应redolog
的写入点- 每次写入长度为length的
redolog
,LSN
就会加上length
- 每次写入长度为length的
LSN
也会写入到数据页中,用来确保数据页不会被多次执行重复的redolog
样例
- 三个并发事务处于
prepare
阶段:tx1
、tx2
、tx3
- 都完成写入
redolog buffer
和持久化到磁盘的过程 - 对应的
LSN
为50
、120
、160
- 都完成写入
tx1
第一个到达,被选为组leader
- 等
trx1
要开始写盘的时候,组内已经有3个事务,LSN
变成了160
trx1
带着LSN=160
去写盘,等trx1
返回时,所有LSN<160
的redolog
都已经持久化到磁盘trx2
和trx3
可以直接返回
小结
- 一次组提交里面,组员越多,节省磁盘的IOPS的效果越好
- 在并发更新场景下,第1个事务写完
redolog buffer
后,接下来的fsync
越晚调用,节省磁盘的IOPS的效果越好
binlog组提交
写binlog
其实分两步:binlog cache -> (write) -> binlog file
+ binlog file -> (fsync) -> disk
MySQL为了让组提交效果更好,延后了fsync
执行时机,两阶段提交细化如下
binlog
也可以支持组提交了,但第3步执行很快,导致了binlog
的组提交效果不如redolog
的组提交效果
参数
1 | mysql> SHOW VARIABLES LIKE '%binlog_group_commit_sync%'; |
binlog_group_commit_sync_delay
:延迟多少微秒才调用fsync
binlog_group_commit_sync_no_delay_count
:累计多少次之后才调用fsync
- 两者关系:或,但当
binlog_group_commit_sync_delay=0
时,binlog_group_commit_sync_no_delay_count
无效
WAL性能
redolog
和binlog
都是顺序写- 组提交机制:可以大幅降低磁盘的IOPS消耗
MySQL的IO瓶颈
- 设置
binlog_group_commit_sync_delay
和binlog_group_commit_sync_no_delay_count
- 故意等待,利用组提交减少IOPS消耗,同时可能会增加语句的响应时间,但没有丢数据风险
sync_binlog=N(100~1,000)
- 主机断电会丢失
binlog
日志
- 主机断电会丢失
innodb_flush_log_at_trx_commit=2
- 主机断电会丢失数据
2
和0
的性能接近,但设置为0
(数据仅在redolog buffer
),在MySQL异常重启时也会丢失数据
crash-safe的保证
- 如果客户端收到事务成功的消息,事务就一定持久化了的
- 如果客户端收到事务失败(主键冲突、回滚等)的消息,事务一定是失败的
- 如果客户端收到执行异常的消息,应用需要重连后通过查询当前状态来继续后续的逻辑
- 数据库只需要保证内部(数据与日志之间,主从之间)一致即可
参考资料
《MySQL实战45讲》
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.