读写分离之后最常见的抱怨是“刚提交的数据查不到”。多数情况就是主从延迟。
先确认是不是延迟
SHOW SLAVE STATUS\G
关注三行:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 12
前两个是 Yes 说明复制线程正常;第三个大于 0 就是延迟。但这个值不准——它比较的是 SQL 线程正在执行的事件时间戳和从库当前时间,如果主库长时间没有写入,它可能显示 0 但其实积压了很多 relay log。
更准的算法:在主库写一个时间戳,从库读出来做差。或者看 SHOW SLAVE STATUS 里的 Relay_Log_Space 是否持续增长。
常见原因
1. 大事务
一条 UPDATE 改了 50 万行,主库几秒钟跑完,从库的 SQL 线程要单线程回放这个事务,可能跑十几分钟。期间所有后面的操作都堵着。
对策:拆成小批量,每次 1000 行,循环处理。
2. 从库没有索引
主库有索引,从库因为某些原因少建了,回放时的 UPDATE/DELETE 变成全表扫描。这类问题很隐蔽,pt-table-checksum 能对比表结构。
3. 从库配置比主库差
给从库配了低配机器,或者混部了其他服务。从库的 innodb_flush_log_at_trx_commit 可以设成 2(牺牲一点安全性换吞吐),前提是能接受宕机丢少量数据。
4. 单线程回放
MySQL 5.7 支持并行复制,但默认参数保守:
slave_parallel_workers = 4
slave_parallel_type = LOGICAL_CLOCK
slave_preserve_commit_order = 1
LOGICAL_CLOCK 按组提交并行,效果比 DATABASE 模式好。preserve_commit_order 保证从库提交顺序和主库一致,避免读到中间状态。
业务上怎么兜
延迟不可能完全消除,业务层要能接受:
- 写后立即读:强制走主库。可以在写操作后把用户 id 放进“最近写过”的集合,读请求检查这个集合
- 最终一致:写完之后返回,前端轮询或用 WebSocket 推送结果
- 容忍延迟的读:列表、统计这类走从库,用户感知不到
监控
写个脚本每分钟采集一次,超过 5 秒告警:
mysql -e "SHOW SLAVE STATUS\G" | grep Seconds_Behind_Master
但别只看这个值,配合 SHOW PROCESSLIST 看 SQL 线程正在执行什么语句,能直接定位到是哪个大事务卡住的。
我那次的元凶是一条没加条件的 UPDATE ... WHERE create_time > '2023-01-01',影响了 30 万行。加上 LIMIT 分批之后恢复正常。
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/archives/669
共有 0 条评论