文章目录
- MySQL45讲 第二十三讲 是怎么保证数据不丢的?
- 一、binlog 写入机制
- (一)事务执行与 binlog cache
- (二)事务提交与 binlog 文件写入
- 二、redo log 写入机制
- (一)事务执行与 redo log buffer
- (二)redo log 的三种状态
- (三)事务提交与 redo log 持久化
- (四)组提交机制
- 三、“双 1” 配置及其他设置
- (一)“双 1” 配置
- (二)其他设置及风险
- 四、相关问题解答
- (一)执行 update 语句后数据未改变
- (二)binlog cache 与 redo log buffer 设计差异
- (三)事务执行期间 crash 对主备一致性的影响
- (四)binlog 写完盘后 crash 的情况
- 五、总结与思考
MySQL45讲 第二十三讲 是怎么保证数据不丢的?
在 MySQL 的世界里,数据的可靠性是至关重要的。今天,我们将深入探讨 MySQL 是如何保证数据不丢的,这涉及到 binlog 和 redo log 的写入机制,以及一些关键参数的设置。
一、binlog 写入机制
(一)事务执行与 binlog cache
事务执行过程中,binlog 先被写到 binlog cache(缓存)。每个线程都有自己独立的 binlog cache,这是因为 binlog 不能被拆开,一个事务的 binlog 必须一次性完整写入。系统通过参数 binlog_cache_size 控制单个线程内 binlog cache 所占内存大小,若超出,需暂存到磁盘。
(二)事务提交与 binlog 文件写入
事务提交时,执行器将 binlog cache 里的完整事务写入 binlog 文件,并清空 binlog cache。这里涉及到 write 和 fsync 两个操作,write 是将日志写入文件系统的 page cache,速度较快;fsync 才是将数据持久化到磁盘,通常认为 fsync 占磁盘 IOPS。write 和 fsync 的时机由参数 sync_binlog 控制。
虽然每个线程有自己的 binlog cache,但所有线程共用同一份 binlog 文件。
- sync_binlog = 0:每次提交事务只 write,不 fsync,由后台线程每秒一次将 page cache 中的数据持久化到磁盘。这种方式在主机异常重启时,可能丢失最近 1 秒内事务的 binlog 日志。
- sync_binlog = 1:每次提交事务都会执行 fsync,保证了数据的安全性,但性能相对较低。
- sync_binlog = N(N>1):每次提交事务 write,但累积 N 个事务后才 fsync。在出现 IO 瓶颈时,可提升性能,但主机异常重启会丢失最近 N 个事务的 binlog 日志。在实际业务场景中,常将 sync_binlog 设置为 100 - 1000 中的某个数值。
二、redo log 写入机制
(一)事务执行与 redo log buffer
事务执行过程中,生成的 redo log 先写到 redo log buffer。与 binlog 不同,redo log buffer 是全局共用的,因为 redo log 中间生成的日志可以写到 buffer 中,且其他事务提交时,redo log buffer 中的内容可 “搭便车” 一起写到磁盘。
(二)redo log 的三种状态
- 存在 redo log buffer 中:物理上在 MySQL 进程内存中。
- 写到磁盘(write)但未持久化(fsync):物理上在文件系统的 page cache 中。
- 持久化到磁盘:对应 hard disk。
(三)事务提交与 redo log 持久化
事务提交时,redo log 的持久化策略由参数 innodb_flush_log_at_trx_commit
控制。
- 设置为 0:事务提交时,
redo log
只留在redo log buffer
中,后台线程每秒一次将redo log buffer
中的日志 write 到page cache
,并调用fsync
持久化到磁盘。若事务执行期间 MySQL 异常重启,这部分日志丢失,但事务未提交,所以无损失。 - 设置为 1:事务提交时,
redo log
直接持久化到磁盘。在redo log
的 prepare 阶段就会进行持久化,这是因为崩溃恢复逻辑依赖于 prepare 的redo log
和binlog
来恢复数据。 - 设置为 2:事务提交时,
redo log
写到page cache
。除后台线程每秒一次的轮询操作外,当redo log buffer
占用空间即将达到innodb_log_buffer_size
一半时,后台线程会主动 write 到page cache
(但不 fsync);并行事务提交时,也会顺带将未提交事务的redo log buffer
持久化到磁盘。
(四)组提交机制
为提升性能,MySQL 引入了组提交机制。以三个并发事务(trx1、trx2、trx3
)为例,在 prepare 阶段,它们都写完 redo log buffer
并持久化到磁盘,此时 LSN 分别为 50、120 和 160。trx1 作为组内第一个到达的事务(leader),当它写盘时,会将 LSN 为 160 及之前的 redo log 都持久化到磁盘,然后 trx2 和 trx3 可直接返回。一次组提交中,组员越多,节约磁盘 IOPS 的效果越好。为让 binlog 也能更好地组提交,MySQL 将 redo log
做 fsync
的时间拖到 binlog write
到磁盘之后,这样 binlog
也可实现组提交,但通常 bin log
组提交效果不如 redo log
,可通过设置 binlog_group_commit_sync_delay
和 binlog_group_commit_sync_no_delay_count
来提升 binlog 组提交效果。
三、“双 1” 配置及其他设置
(一)“双 1” 配置
通常所说的 MySQL “双 1” 配置,即sync_binlog
和 innodb_flush_log_at_trx_commit
都设置为 1。这意味着一个事务完整提交前,需要等待两次刷盘,一次是 redo log
(prepare 阶段),一次是 binlog
,保证了数据的安全性,但对磁盘 IOPS 要求较高。
(二)其他设置及风险
- 提升 binlog 组提交效果:设置
binlog_group_commit_sync_delay
和binlog_group_commit_sync_no_delay_count
参数,可减少 binlog 写盘次数,但可能增加语句响应时间,不过无数据丢失风险。 - sync_binlog 大于 1:将
sync_binlog
设置为大于 1 的值(如 100 - 1000),可提升性能,但主机掉电时会丢 binlog 日志。 - innodb_flush_log_at_trx_commit 设置为 2:可提升性能,主机掉电时可能丢数据,但相比设置为 0 风险更小,因为设置为 0 时,redo log 只保存在内存中,MySQL 异常重启也会丢数据。
四、相关问题解答
(一)执行 update 语句后数据未改变
执行 update 语句后,通过 hexdump 命令查看 ibd 文件内容未发现数据改变,这是因为 WAL 机制,update 语句执行完成后,InnoDB 可能只保证写完了 redo log 和内存,还未将数据写到磁盘。
(二)binlog cache 与 redo log buffer 设计差异
**binlog 是不能被打断的,一个事务的 binlog 必须连续写,所以每个线程自己维护 binlog cache,事务完成后再一起写入文件。**而 redo log 没有此要求,中间生成的日志可写到全局共用的 redo log buffer 中,且可借助其他事务提交时 “搭便车” 写到磁盘。
(三)事务执行期间 crash 对主备一致性的影响
事务执行期间,若还未到提交阶段发生 crash,redo log 虽可能丢失,但 binlog 也在 binlog cache 中未发给备库,从业务角度看事务未提交,所以主备数据是一致的。
(四)binlog 写完盘后 crash 的情况
binlog 写完盘后发生 crash,若未给客户端答复就重启,客户端重连后发现事务已提交成功,这并非 bug。因为数据库的 crash - safe 保证若客户端收到事务成功消息,事务一定持久化;若收到事务失败消息,事务一定失败;若收到 “执行异常” 消息,应用需重连后查询当前状态继续后续逻辑,此时数据库只需保证内部数据和日志、主库和备库之间一致即可。
五、总结与思考
通过对 binlog 和 redo log 写入机制的详细介绍,以及对相关参数的分析,我们了解到 MySQL 如何从多个方面保证数据不丢。“双 1” 配置提供了较高的数据安全性,但在性能和 IO 资源上有一定要求;其他设置虽可提升性能,但伴随着不同程度的数据丢失风险。