读写分离无法解决主从延迟导致的“写后即读”不一致问题,需强制关键读走主库;分库分表后JOIN/GROUP BY失效,应拆解逻辑、用预聚合表和游标分页替代。

MySQL读写分离后,主从延迟导致查不到刚写入的数据
读写分离本身不解决数据一致性问题,只是把查询压力分出去。一旦应用在写完主库后立刻去从库查,大概率查不到——因为从库还没同步过来。
常见错误现象:SELECT 返回空结果,但 SELECT 主库能查到;监控显示 Seconds_Behind_Master 持续几十秒甚至几分钟。
- 关键判断点:不是所有读请求都能甩给从库,尤其是「写后即读」(read-after-write)场景,必须强制走主库
- 简单方案:在事务提交后,用同一个数据库连接(或显式指定主库)执行后续关键查询;不要依赖全局路由中间件自动分发
- 进阶做法:应用层维护一个轻量级的「写后路由表」,比如对某个
user_id在接下来 5 秒内所有读请求都打向主库 - 注意
semi-sync只保证至少一个从库收到日志,并不保证已执行;真正降低延迟靠的是减少大事务、避免长更新、调小slave_parallel_workers的调度开销
分库分表后,JOIN 和 GROUP BY 查询没法直接用了
物理上数据散在多个库/表里,MySQL 原生不支持跨分片的复杂查询。强行让中间件做归并,性能差、内存爆、超时多。
使用场景:订单列表页要按用户维度查 + 统计每个用户的订单数 + 关联用户昵称——这类需求在单库是 JOIN users ON orders.user_id = users.id GROUP BY users.id,分库后就失效。
- 优先拆解逻辑:把「统计」和「详情展示」拆成两个接口,统计走预聚合表(如每小时跑一次任务写入
user_order_summary),详情查单个分片 - 避免跨分片
ORDER BY ... LIMIT:它需要拉取所有分片前 N 条再合并排序,N 越大越慢;改用基于时间/ID 的游标分页(WHERE created_at > ? ORDER BY created_at LIMIT 20) -
UNION ALL不是银弹:虽然语法上能拼多个分片结果,但网络往返多、应用内存压力大,仅适用于分片数少(≤4)、结果集小(
高并发下 INSERT ... SELECT 或 UPDATE ... WHERE IN (subquery) 触发全表锁
这类语句在 RR 隔离级别下容易升级为间隙锁(gap lock)甚至临键锁(next-key lock),锁住一大片索引范围,后面所有相关 INSERT/UPDATE 都得排队等。
典型错误现象:监控看到大量线程卡在 Waiting for table metadata lock 或 Waiting for a row lock,而 SHOW ENGINE INNODB STATUS 显示持有锁的事务正在执行带子查询的更新。
- 根本原因:子查询没走索引,或优化器误判导致扫描行数远超预期;RR 级别下 MySQL 会对扫描到的所有索引间隙加锁
- 实操建议:先用
EXPLAIN看子查询是否走了索引;没走就加索引,或者把子查询结果先查出来,在应用层拼成WHERE id IN (1,2,3...)再执行 - 批量操作别贪大:
IN列表超过 500 项就拆成多批;INSERT ... SELECT改为应用层分页查+循环插入,控制每次最多 100 行 - 临时方案:如果业务允许短暂一致性妥协,可将该会话隔离级别临时设为
READ-COMMITTED(SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED),但需确认无幻读风险
分库分表中间件(如 ShardingSphere、MyCat)配置不当反而放大锁竞争
中间件不是黑盒,它转发 SQL、重写语句、归并结果的过程本身就有开销,配置错一个参数,可能让本该分散的锁集中在某一个分片上。
常见坑:所有订单按 user_id 分片,但运营后台按 order_status 批量更新,结果所有请求都落到同一个分片,锁争抢比单库还猛。
- 分片键选择必须匹配核心写入路径:高频更新/查询字段优先作为分片键,避免「热点分片」;像
order_status这种低基数字段绝不能当分片键 - 慎用广播表(broadcast table):比如
dict_city表被设为广播表,每次更新都要同步到所有分片,写放大严重;只在真正读多写极少且数据量小( - 关闭不必要的功能:如 ShardingSphere 的
sql-show: true在生产环境必须关,否则每条 SQL 都打日志,IO 拖垮性能;props.sql-simple: true可减少日志冗余 - 连接池别混用:中间件的连接池和应用直连 MySQL 的连接池不能共享配置;中间件侧要调大
maxPoolSize(建议 ≥20),否则并发一上来就卡在获取连接上
分库分表和读写分离不是加了就能扛住流量,真正的难点在于识别哪些查询无法平移、哪些一致性要求必须绕过架构限制、以及中间件配置和业务逻辑之间那些隐含的耦合点——这些地方不提前压测、不看慢日志、不盯 InnoDB Status,上线后问题只会更难定位。










