读写分离未生效主因是路由规则未配置或事务内查询强制走主库;主从延迟超1秒读从库易脏读;分库后跨节点JOIN需应用层聚合;扩容须用pt-online-schema-change+GTID;分片键应选高基数字段并支持动态扩容。

读写分离没生效?先确认主从延迟和事务一致性
很多团队一上读写分离就以为流量自动分流了,结果发现 SELECT 还是全打到主库。根本原因是没配对中间件路由规则,或应用层手动指定了连接源。更隐蔽的问题是:在事务中执行了 SELECT,而事务内所有查询默认走主库(否则会读到过期数据)。SHOW SLAVE STATUS 中的 Seconds_Behind_Master 超过 1 秒时,读从库就可能返回脏数据——这不是配置问题,是架构约束。
实操建议:
- 用
mysqlrouter或ShardingSphere-Proxy替代应用层硬编码路由,避免每个服务重复实现逻辑 - 对非事务性、允许秒级延迟的查询(如后台报表、用户资料页),显式走从库;对余额、订单状态等强一致场景,强制走主库
- 监控
Seconds_Behind_Master和从库Threads_connected,延迟突增时自动熔断从库读流量
分库分表后跨节点 JOIN 失效?别硬写 SQL,改用应用层聚合
MySQL 原生不支持跨分片 JOIN,ShardingSphere 或 MyCat 的“逻辑表”能力只覆盖简单等值关联,一旦涉及 GROUP BY、子查询或非分片键条件,就会退化成广播查询,性能断崖下跌。你写的 SELECT * FROM order o JOIN user u ON o.user_id = u.id 在分片后大概率查不出数据,或者耗时暴涨。
实操建议:
- 分片键设计必须对齐业务高频查询路径,比如订单按
user_id分片,就别指望用order_no高效查用户信息 - 把“需要 JOIN”的逻辑拆到应用层:先查分片后的
order列表,提取一批user_id,再批量查user表(注意控制批量大小,避免从库压力过大) - 冷热数据分离:用户基础信息(
user)单库集中存储,订单(order)分库分表,通过异步任务维系宽表,供复杂查询使用
扩容时如何不锁表、不丢数据?用 pt-online-schema-change + GTID 切流
直接 ALTER TABLE 在生产环境等于停服,尤其大表加索引或改字段类型,MDL 锁会让所有 DML 挂起。有人试过 ALGORITHM=INPLACE,但 MySQL 5.7+ 对某些操作仍需拷贝全表。真正平滑的方式不是“快”,而是“可中断、可回滚、流量无感”。
实操建议:
- 必须开启
GTID(gtid_mode=ON),否则主从切换时位点难对齐,扩容后新旧分片数据不一致 - 用
pt-online-schema-change执行 DDL:它创建影子表、同步增量、原子切换,全程不锁原表;但要注意--chunk-size别设太大,避免从库复制延迟加剧 - 切流前,在新分片上预跑一周真实流量(读+写),用
pt-table-checksum校验新旧数据一致性
分片键选错导致数据倾斜?用复合分片 + 动态扩容策略兜底
只用 user_id % 4 分 4 库,结果头部 100 个大 V 用户占了 60% 订单量,某个分片 CPU 持续 95%,其他空转。这不是分片数量不够,是分片键没覆盖业务熵值。更麻烦的是,等发现倾斜再改分片规则,等于重做整个迁移流程。
实操建议:
- 优先选高基数、分布均匀、查询频率高的字段,比如
order_id(雪花算法生成)比status更适合作分片键 - 用复合分片:先按
tenant_id分库,再按order_id分表,既隔离租户,又分散单库压力 - 预留“动态扩容”能力:分片路由层支持运行时加载新分片配置,扩容时只需增加物理库 + 更新配置中心,不用重启服务
分库分表不是加机器就能解决的银弹,真正的平滑在于:分片逻辑是否可灰度验证、数据迁移是否可逆、故障时能否快速切回单库模式。这些细节,往往比选哪个中间件更重要。











