读写分离和分库分表

数据库读写分离 & 分库分表知识点总结

适用场景:大数据量、高并发读写更新场景;读写分离解决读压力;分库分表解决单库单表容量、IO、QPS瓶颈;二者均属于数据库架构层优化,优先完成SQL、索引、缓存、冷热归档之后再使用。

一、读写分离

1. 核心架构

  • 主库:负责写操作 INSERT / UPDATE / DELETE,承担强一致性读。
  • 从库:通过binlog主从复制同步主库数据,承担大部分读请求。
  • 实现方式:应用层AOP注解;数据库中间件(Sharding‑Sphere‑Proxy、MyCat)。

2. 常见问题与解决方案

2.1 主从复制延迟(最常见)

现象:写入主库后立刻查询从库,读到旧数据。
成因:网络延迟、大事务、从库回放速度慢、主库压力大、硬件差异。
解决方案

  1. 强一致性查询强制走主库;
  2. 会话一致性:同一会话写之后短时间读路由主库;
  3. MySQL5.7+开启并行复制,避免主库大事务;
  4. 监控Seconds_Behind_Master,延迟超阈值自动切读流量回主库;
  5. 业务容忍毫秒‑秒级不一致的场景正常走从库。

2.2 SQL路由错误

现象:写语句路由到从库报错;复杂SQL解析异常。
解决方案

  1. 中间件模式:依靠SQL语法解析,复杂SQL配置黑名单;
  2. 应用层:使用@Master/@Slave注解显式指定数据源,可控性高;
  3. 告警拦截从库写操作。

2.3 事务内路由问题(高频坑)

现象:事务内部select被路由到从库,造成数据错乱。
解决方案:只要开启事务,全部SQL强制走主库,事务外SELECT才走从库。

2.4 主从数据不一致(数据漂移)

成因:从库人为写操作、binlog statement模式bug、随意跳过复制错误。
解决方案

  1. binlog设置为ROW行模式;
  2. 从库开启super_read_only,禁止任何写入;
  3. 使用pt‑table‑checksum定期校验主从数据;
  4. 禁止随意使用sql_slave_skip_counter。

2.5 从库负载不均衡

现象:部分从库压力过高,部分空闲;慢查询影响业务从库。
解决方案

  1. 配置权重、轮询、随机负载策略;
  2. 报表统计类慢查询使用独立分析从库;
  3. 监控从库CPU/IO,过载自动摘除节点。

2.6 主库故障切换高可用

现象:主库宕机,需要提升从库为新主库,应用连接不更新。
解决方案

  1. 传统主从架构:使用MHA实现自动故障检测与选主;
  2. MySQL 5.7.17+:可使用MGR(MySQL Group Replication)组复制实现自动故障切换与选主;
  3. VIP虚拟IP漂移,应用无需修改IP;
  4. MHA场景下,旧主恢复后需手动执行CHANGE MASTER指向新主,重新作为从库接入。

2.7 半同步复制(数据安全保障)

  • 半同步复制要求主库提交事务前至少等待一个从库确认收到binlog,可显著降低主库宕机时的数据丢失风险;
  • 半同步复制不能消除主从延迟(只保证binlog被接收,不保证从库已回放完成),本质是数据安全机制而非延迟优化手段;
  • 适用于对数据丢失零容忍的核心业务(交易、支付等)。

2.8 新建从库压垮主库

解决方案:低峰期使用Percona XtraBackup物理备份,限速备份IO。

2.9 主从复制不等于备份

  • 主从复制不能替代定时全量备份;主库故障可能出现数据丢失,半同步复制降低丢失概率。

3. 读写分离选型对比

方案 优点 缺点 适用场景
应用层AOP注解 无中间件单点,轻量 代码侵入,依赖开发规范 中小规模系统
中间件代理 业务无侵入,统一路由 中间件需要集群防单点 中大型系统

二、分库分表

定位:最后的优化手段;优先做SQL优化、冷热归档、缓存、读写分离之后再考虑。
触发参考阈值:单表5000万~1亿行;单库QPS、连接数、磁盘IO持续打满无法缓解。

1. 分类

  1. 垂直拆分:
    • 垂直分库:不同业务模块拆分到不同数据库;
    • 垂直分表:大表拆分,大字段、低频字段拆出。
  2. 水平拆分:同一张逻辑表,按分片规则打散到多个物理库表(最常用);分片方式:hash取模、范围分片、复合分片。

2. 常见问题与解决方案

2.1 分片键选择错误(根源性问题)

现象:数据倾斜热点分片;大量查询不带分片键触发全分片扫描。
解决方案

  1. 分片键选择高基数、分布均匀字段(user_id、order_id);
  2. 90%核心查询条件带上分片键;
  3. 时间分片容易热点,可使用时间+ID复合分片;
  4. 上线前梳理全部查询场景,分片键修改成本极高。

2.2 跨分片查询,全分片扫描

现象:查询不带分片键,SQL下发所有分片执行,性能差。
解决方案

  1. DAO层校验,核心查询强制携带分片键;
  2. 异构索引映射表:通过映射表查询得到分片键再路由;
  3. 多维、后台查询不走业务分片库,同步至ES/数仓。

2.3 跨分片分页、排序、聚合

现象:深分页limit offset,size性能灾难;count/sum/group by跨分片合并开销大。
解决方案

  1. 游标分页代替offset深分页:where id < last_id limit size;业务限制最大翻页;
  2. count/sum使用冗余计数表;复杂统计走Flink/ClickHouse/Doris离线计算;
  3. group by尽量带上分片键。

2.4 跨分片JOIN

现象:两张表分布在不同分片,数据库原生不支持跨库join。
解决方案

  1. 字段冗余(反范式),避免join;
  2. 应用层组装,批量in查询,避免N+1;
  3. ER绑定分片:父子表使用相同分片键,保证关联数据落在同一分片;
  4. 复杂关联查询下沉ES/数仓。

2.5 分布式事务

现象:一个业务操作写入多个分片,单机事务失效,部分成功部分失败。
核心原则:能规避就规避,优先最终一致性
解决方案

  1. 业务设计让同一事务操作落在同一个分片;
  2. 最终一致性方案:本地消息表、RocketMQ事务消息;消费端必须幂等;
  3. 强一致方案(性能差):Seata‑AT、TCC;XA两阶段尽量少用。

2.6 分布式ID

现象:数据库自增ID在多表会重复。
解决方案

  1. 雪花算法Snowflake:高性能趋势递增,处理时钟回拨;
  2. 号段模式(Leaf):数据库预分配ID号段;
  3. Redis INCR;不推荐UUID(聚簇索引页分裂)。

2.7 扩容 & 数据迁移

现象:分片数量不足,需要重新rehash迁移数据,停机风险。
解决方案

  1. 前期预估容量,分片数预留余量;
  2. 一致性哈希减少迁移数据量;
  3. 不停机迁移标准流程:双写新旧集群 → 全量迁移历史数据 → 数据校验 → 切读流量 → 下线旧集群;必须支持回滚。

2.8 全局唯一约束失效

现象:唯一索引仅单分片生效,无法全局保证唯一性。
解决方案

  1. 分片键本身作为唯一键;
  2. 建立独立唯一键映射表;Redis辅助校验;
  3. 数据库层面无法实现跨分片唯一约束,依靠应用层保障。

2.9 数据倾斜、热点分片

现象:个别分片数据量、QPS远高于其他分片(热点用户、时间分片)。
解决方案

  1. 热点key加盐二次分片;
  2. 热点数据路由至专用分片或使用缓存;
  3. 监控各分片数据量与QPS。

2.10 运维复杂度上升

现象:DDL变更、备份、故障排查、数据定位成本高。
解决方案

  1. 使用成熟中间件 Sharding‑Sphere,禁止自研分片;
  2. 在线DDL工具 gh‑ost / pt‑online‑schema‑change(Percona Toolkit),批量执行分片变更;
  3. 完善分片路由工具、全分片监控(慢查询、连接数、数据量)。

3. 优化优先级

SQL索引优化 → 冷热数据归档 → 缓存 → 读写分离 → 分区表 → 分库分表

三、整体架构踩坑总结

  1. 读写分离最大风险:主从延迟、事务路由错误、从库误写;
  2. 分库分表最大风险:分片键选错、跨分片查询、分布式事务、扩容迁移;
  3. 二者都只是分摊压力,不会提升单条SQL本身性能;SQL写得差,分片后依然慢;
  4. 分布式问题(事务、join、唯一约束)大多靠业务设计规避,而不是靠中间件硬解决。
posted @ 2026-09-05 23:16  灰马非马  阅读(24)  评论(0)    收藏  举报