主从同步延迟导致select读到旧数据,因mysql主从复制异步,主库commit后从库可能未执行relay log;解决方法是“刚写完立刻读”时强制走主库,可用/+ force_master /注释或threadlocal标记写操作自动路由。

为什么主从同步延迟会让 SELECT 查到旧数据
读写分离后,应用把 SELECT 发给从库,但 MySQL 主从复制是异步的。主库 COMMIT 后,从库可能还没收到 binlog,更没执行完 relay log。此时读从库,就会返回过期结果。
- 典型现象:用户刚提交订单(写主库),刷新页面查订单列表(读从库)却看不到
- 不是网络慢,而是从库
Seconds_Behind_Master非零,且受大事务、从库负载高、网络抖动影响 - 不能靠“等几毫秒”来缓解——延迟波动大,
SLEEP(0.1)既不准又拖慢响应 - 真正可控的方式是:对“刚写完立刻要读”的场景,强制走主库(即
SELECT ... FOR UPDATE或显式路由到主库)
怎么让应用层自动判断该读主库还是从库
硬编码 if (isWriteFollowed()) { useMaster(); } 不可维护。推荐用中间件或 SDK 级别识别“强一致性读”上下文。
- MyCat、ShardingSphere-JDBC 支持
/*+ FORCE_MASTER */注释,SQL 中加这个就能穿透到主库 - Spring Boot + MyBatis 可结合
ThreadLocal标记当前事务是否含写操作,后续同一线程的SELECT自动路由主库 - 避免用 SQL 解析判断(比如检测是否有
INSERT/UPDATE),因为预编译、存储过程、批量操作会让规则失效 - 注意:事务内所有读必须走同一节点,否则会违反 ACID;即使只读事务,若开启
REPEATABLE READ且跨节点,快照视图也不一致
max_connections 和从库线程数配置不当会直接压垮从库
读流量全打到从库,但默认 max_connections=151,加上 slave_parallel_workers=0(MySQL 5.7 默认),等于单线程回放 + 连接数瓶颈。
- 先调高从库
max_connections,至少是主库的 2–3 倍(例如主库 500,从库设 1500) - MySQL 5.7+ 必须开并行复制:
slave_parallel_type=LOGICAL_CLOCK+slave_parallel_workers=4~8(根据 CPU 核数) - 监控关键指标:
SHOW SLAVE STATUS\G中的Seconds_Behind_Master、Relay_Log_Space(过大说明 IO 或 SQL 线程卡住) - 不要盲目增加
slave_parallel_workers到 16+,超过物理核数反而因锁争用降低吞吐
连接池配置不区分主从,等于白做读写分离
很多项目只配了一个 HikariCP 数据源,再用 AOP 切换 URL——这会导致连接池无法复用、频繁重建连接,CPU 和 TIME_WAIT 暴涨。
- 必须为
master和每个slave单独声明数据源 Bean,各自配独立连接池(maxPoolSize、connectionTimeout) - 读写分离路由逻辑不能放在 DAO 层,应在数据源抽象层(如
AbstractRoutingDataSource)完成,确保事务内连接不切换 - 从库连接池的
maxLifetime建议比主库短(例如 1800000ms → 900000ms),避免长连接在主从切换后仍连着已下线的从库 - 健康检查不能只 ping,要执行
SELECT 1并校验Slave_IO_Running和Slave_SQL_Running状态
@Transactional 方法里混着写和读,又没强制主库读,就必然踩到一致性坑。这点没法靠 DBA 调优解决,得开发和架构一起对齐语义。











