当前位置: 代码网 > it编程>数据库>Mysql > MySQL保证数据不丢与主备一致的方法步骤

MySQL保证数据不丢与主备一致的方法步骤

2026年09月01日 Mysql 我要评论
mysql是怎么保证数据不丢的?一、问题背景业务高峰期临时提升性能的方法,往往与数据的可靠性有关。前置结论:只要 redo log 和 binlog 保证持久化到磁盘,就能确保 mysql 异常重启后

mysql是怎么保证数据不丢的?

一、问题背景

业务高峰期临时提升性能的方法,往往与数据的可靠性有关。
前置结论:只要 redo log 和 binlog 保证持久化到磁盘,就能确保 mysql 异常重启后数据可以恢复。

本文聚焦一个问题:binlog 和 redo log 是怎么写入磁盘的?如何保证真实落盘?

二、binlog 的写入机制

写入逻辑

  • 事务执行过程中,先把日志写到 binlog cache
  • 事务提交时,把 binlog cache 的完整事务一次性写入 binlog 文件,并清空 cache。

一个事务的 binlog 不能被拆开,不论事务多大,都要一次性写入。每个线程独占一份 binlog cache,大小由 binlog_cache_size 控制,超出则暂存磁盘。

上图要点:每个线程有自己的 binlog cache,但共用同一份 binlog 文件。

write 与 fsync 的区别

操作含义性能
write写入文件系统 page cache,未持久化
fsync数据持久化到磁盘慢,占 iops

由参数 sync_binlog 控制时机:

sync_binlog 取值行为
0每次提交只 write,不 fsync
1每次提交都 fsync(双1之一
n(>1)累积 n 个事务后 fsync

取舍:io 瓶颈场景下调大可提升性能,但主机异常重启会丢失最近 n 个事务的 binlog,常见取值 100~1000。

三、redo log 的写入机制

事务执行中生成的 redo log 先写到 redo log buffer,无需每次生成后立即落盘(事务未提交,丢了也无损失)。

redo log 的三种状态

  1. 红色:在 redo log buffer 中(mysql 进程内存);
  2. 黄色:write 到 page cache,未 fsync;
  3. 绿色:持久化到磁盘。

由参数 innodb_flush_log_at_trx_commit 控制:

取值行为
0提交时只留在 redo log buffer
1提交时直接持久化到磁盘(双1之一
2提交时 write 到 page cache

未提交事务的 redo log 何时会被落盘?

除了后台线程每秒一次轮询刷盘外,还有两种场景:

  1. buffer 占用达到一半:后台线程主动 write 到 page cache(只 write,不 fsync);
  2. 并行事务提交顺带刷盘:事务 b 提交时(=1),会把 redo log buffer 中事务 a 的日志一起持久化。

两阶段提交时序:redo log prepare → 写 binlog → redo log commit。
=1 时,prepare 阶段就要 fsync(崩溃恢复依赖 prepare redo log + binlog)。commit 阶段只需 write 到 page cache 即可。

四、组提交:tps 为何能远超磁盘 iops

疑问

"双1"配置下,一个事务提交前要两次刷盘(redo log prepare + binlog)。tps 2万岂不是要写4万次磁盘?但磁盘能力只有2万左右,怎么实现?

lsn 与组提交

lsn(log sequence number):单调递增的日志逻辑序列号。每次写入长度 length,lsn 加 length。

三个事务的组提交过程

以 trx1(lsn=50)、trx2(lsn=120)、trx3(lsn=160) 为例:

  1. trx1 先到,被选为 leader
  2. trx1 要写盘时,组内已有 3 个事务,lsn 已到 160;
  3. trx1 写盘时带的是 lsn=160,返回时所有 ≤160 的 redo log 都已落盘;
  4. trx2、trx3 直接返回,无需各自 fsync

组员越多,节约 iops 效果越好。单线程压测则只能一次一持久化。

"拖时间"优化

为让一次 fsync 带的组员更多,mysql 把 redo log 的 fsync 时机推迟到 binlog 写入之后

写 binlog 分两步:① binlog cache → binlog 文件(write);② fsync。优化后第4步 fsync 时,多个事务的 binlog 可一起持久化。

通过参数增强 binlog 组提交效果(或关系,满足其一即 fsync):

参数含义
binlog_group_commit_sync_delay延迟多少微秒再 fsync
binlog_group_commit_sync_no_delay_count累积多少次再 fsync(设为0则该参数失效)

通常 binlog 组提交效果不如 redo log,因为 write 与 fsync 间隔短,能聚合的事务较少。

wal 机制为何能减少磁盘写?

  1. redo log 与 binlog 都是顺序写,远快于随机写;
  2. 组提交机制大幅降低 iops 消耗。

五、io 瓶颈下的三种优化方法

方法原理风险
调大 binlog_group_commit_sync_delay_count故意等待,攒批 fsync增加响应时间,无丢数据风险
sync_binlog 设为 >1(100~1000)累积 fsync主机掉电丢失最近 n 个事务 binlog
innodb_flush_log_at_trx_commit 设为 2write 到 page cache主机掉电丢数据

不建议设为 0:redo log 只留内存,mysql 本身异常重启也会丢数据。设为 2 性能与 0 相近,但 mysql 异常重启不丢数据,风险更小。

"双1"配置sync_binlog=1 + innodb_flush_log_at_trx_commit=1,事务提交前两次刷盘,数据最可靠。

mysql是怎么保证主备一致的?

一、问题背景

binlog 可以用来归档,也可以做主备同步。但 binlog 的内容是什么样的?为什么备库执行了 binlog 就能跟主库保持一致?

毫不夸张地说,mysql 能成为最流行的开源数据库,binlog 功不可没。几乎所有高可用架构都直接依赖 binlog,且都是从最基本的一主一备演化而来。

理解背后的设计原理,也可以从业务开发的角度借鉴这些设计思想。本文结合数仓测试中主备同步、数据一致性验证的场景,梳理主备基本原理、binlog 三种格式对比,以及双 m 结构的循环复制问题。

二、mysql 主备的基本原理

2.1 基本主备切换流程

  • 状态 1:客户端读写都访问节点 a,节点 b 是 a 的备库,只同步 a 的更新到本地执行,保持与 a 数据相同。
  • 状态 2(切换后):客户端读写访问节点 b,节点 a 成为 b 的备库。

2.2 备库为什么建议设为 readonly

即使 b 不直接被访问,仍建议把备库设为 readonly(只读),原因有三:

  1. 防止运营类查询误操作落到备库;
  2. 防止切换逻辑有 bug(如双写)造成主备不一致;
  3. 可用 readonly 状态判断节点角色。

疑问:备库只读,还怎么跟主库同步更新?
解答readonlysuper 权限用户无效,而用于同步更新的线程拥有超级权限,不受影响。

2.3 主备同步的完整流程

一个 update 语句从节点 a 同步到节点 b 的完整过程:

  1. 备库 b 执行 change master,设置主库 a 的 ip、端口、用户名、密码,以及开始请求 binlog 的位置(文件名 + 偏移量);
  2. 备库 b 执行 start slave,启动两个线程:io_threadsql_threadio_thread 负责与主库建立连接;
  3. 主库 a 校验用户名、密码后,按备库传来的位置读取本地 binlog 并发送给 b;
  4. 备库 b 拿到 binlog 后写入本地文件,称为中转日志(relay log)
  5. sql_thread 读取 relay log,解析并执行其中的命令。

注:多线程复制方案引入后,sql_thread 演化为多个线程,本文不展开。

三、binlog 的三种格式对比

binlog 有三种格式:statementrowmixed(前两者的混合)。先建表初始化数据用于演示:

create table `t` (
  id int(11) not null,
  a int(11) default null,
  t_modified timestamp not null default current_timestamp,
  primary key (`id`),
  key `a` (`a`),
  key `t_modified` (`t_modified`)
) engine=innodb;

insert into t values(1,1,'2018-11-13'),(2,2,'2018-11-12'),
                    (3,3,'2018-11-11'),(4,4,'2018-11-10'),(5,5,'2018-11-09');

测试语句(含注释,客户端需加 -c 参数):

delete from t /*comment*/ where a>=4 and t_modified<='2018-11-10' limit 1;

3.1 statement 格式

binlog_format=statement 时,binlog 记录的是 sql 语句原文。用以下命令查看:

show binlog events in 'master.000001';

内容要点:

  • set @@session.gtid_next='anonymous':gtid 相关,暂忽略;
  • begincommit:标识一个事务;
  • 自动添加的 use 'test':保证备库无论在哪个库,都能正确更新到 test 库的表 t;
  • 真实执行的 delete 语句(含注释原文);
  • commit /* xid=61 */:xid 用于标识事务正确提交。

statement 的隐患(unsafe)

上面语句执行产生 warning:statement 格式 + limit = unsafe。原因:

  • 若主库用索引 a,删除的是 a=4(id=4)这一行;
  • 若备库用索引 t_modified,删除的是 t_modified='2018-11-09'(id=5)这一行。

记录的是语句原文,主备可能选不同索引,导致主备数据不一致

3.2 row 格式

改为 binlog_format='row' 后,binlog 不再记录 sql 原文,而是两个 event:

  • table_map:说明接下来操作的是哪个库的哪张表;
  • delete_rows:定义删除行为。

mysqlbinlog 解析查看详细信息(-vv 解析出字段值):

mysqlbinlog -vv data/master.000001 --start-position=8900

关键信息:

  • server id 1:事务在 server_id=1 的实例上执行;
  • 每个 event 有 crc32 校验值(binlog_checksum=crc32);
  • 每张表对应一个 table_map event,映射到单独数字;
  • binlog_row_image=full(默认)时,delete event 包含被删行的所有字段值;设为 minimal 则只记录必要信息(如 id=4);
  • 末尾 xid event 表示事务正确提交。

优点:row 格式记录真实删除行的主键 id,备库一定删除 id=4 那行,不存在主备删不同行的问题

3.3 mixed 格式

为什么需要 mixed?

  • statement 可能导致主备不一致 → 想用 row;
  • 但 row 太占空间:删 10 万行,statement 只记一条 sql(几十字节),row 要记 10 万行,耗 io、影响速度。

mixed = 折中方案:mysql 自动判断 sql 是否可能引起主备不一致,可能不一致就用 row,否则用 statement。

结论:线上若还是 statement,基本可认为是不合理设置,至少应设为 mixed;而越来越多的场景要求设为 row,一个重要好处就是便于恢复数据

3.4 三种格式下的数据恢复对比

操作row 格式 binlog 记录恢复方法
delete(误删)被删行的整行信息把 delete 转成 insert,插回数据
insert(误插)所有字段信息,可定位插入行把 insert 转成 delete,删掉误插行
update(误改)修改前 + 修改后的整行数据对调前后两行信息再执行即可回滚

mariadb 的 flashback 工具即基于此原理回滚数据。

3.5 mixed 下的 now() 一致性保证

insert into t values(10,10,now());

mixed 格式下这条语句被记录为 statement。担忧:binlog 过 1 分钟才传到备库,主备数据不就不一致了?

事实是 binlog 多记了一条命令:

set timestamp=1546103491;

通过 set timestamp 约定了 now() 的返回时间,无论 1 分钟后还是 3 天后执行,插入值都固定,确保主备一致

风险提示:直接把 mysqlbinlog 解析出的 statement 语句拷贝出来执行是有风险的,因为有些语句依赖上下文命令。正确做法是把解析结果整个发给 mysql 执行:

mysqlbinlog master.000001 --start-position=2738 --stop-position=2973 | mysql -h127.0.0.1

四、循环复制问题(双 m 结构)

生产上更常见的是双 m 结构(a、b 互为主备),切换时无需修改主备关系:

问题

节点 a 更新生成 binlog → 发给 b → b 执行后也生成 binlog(log_slave_updates=on)→ a 又把 b 新生成的 binlog 执行一次 → 无限循环

解决方案:基于 server id

mysql 在 binlog 中记录了命令第一次执行所在实例的 server id,配合以下逻辑打破循环:

  1. 两个库的 server id 必须不同,否则不能设为主备;
  2. 备库重放 binlog 生成新 binlog 时,沿用原 binlog 的 server id
  3. 每个库收到主库发来的日志后,先判断 server id,若与自己的相同(说明是自己生成的),则直接丢弃

双 m 下的执行流

  1. 节点 a 更新的事务,binlog 记的是 a 的 server id;
  2. 传到 b 执行后,b 生成的 binlog server id 仍是 a 的;
  3. 再传回 a,a 判断 server id 与自己的相同 → 丢弃,死循环在此断开。

核心总结

主题关键点结论
主备流程主库 binlog → 备库 io_thread 写 relay log → sql_thread 执行relay log 应用后可丢弃
statement记录 sql 原文带 limit/不确定索引时主备可能不一致(unsafe)
row记录 table_map + 行 event(主键/整行)主备一致,空间大,便于数据恢复
mixed自动判断选 statement 或 row折中方案,建议至少用 mixed,推荐 row
双 m 循环复制基于 server id 判断并丢弃自身日志两节点 server id 必须不同

以上就是mysql保证数据不丢与主备一致的方法步骤的详细内容,更多关于mysql数据不丢与主备一致的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com