从单库瓶颈说起:为什么需要分层扩展
大多数业务起步时,一个数据库实例配上主从复制就足够。但当用户量和数据量达到一定程度,单库写并发过高、单表行数超过千万级、查询变慢,单纯的垂直扩容(加核加内存)很快触及成本和物理上限。这时架构师需要做出选择:是先做读写分离,还是直接上分库分表?
实际落地中,这两者并非互斥,而是递进关系。读写分离解决的是读负载的线性扩展问题,分库分表解决的是写和存储的纵向拆分问题。本文从真实演进视角,拆解每一步的决策依据、技术方案、落地风险以及回滚手段。
第一站:读写分离——低成本抬升读瓶颈
为什么先做读写分离
大多数互联网应用的特点是读多写少,写操作占比可能只有10%~20%。当读QPS打到主库无法承受时,挂从库并通过中间件或客户端路由将读请求分散到从库,是性价比最高的方案。主从复制延迟通常在毫秒级别,业务层可接受短暂不一致。
落地方式
- 客户端方案:在ORM或DAO层硬编码,写走主库,读走从库列表(轮询或随机)。简单但耦合度高,切换从库需重启。
- 中间件方案:部署MyCat、ProxySQL、MySQL Router或ShardingSphere-JDBC(嵌入应用)。中间件处理SQL解析、读写分离与负载均衡,对业务透明。
必须注意的风险
- 主从延迟:刚写入的数据立即读取可能读到旧值。解决方案:强制读主库、延迟容忍或写后休眠(不推荐大流量场景)。
- 从库高可用:从库宕机时中间件需要自动剔除,否则读请求失败。
- 一致性场景:金融级一致性要求写入后必须读到自己写的数据,读写分离不适用,需走主库。
第二站:分库分表——突破写与存储的墙
什么时候必须分
当出现以下情况之一,读写分离已经无法解决:
- 单库写TPS达到上限(MySQL单机写入极限约2w TPS);
- 单表数据量超过2000万行且索引效率下降明显; li>历史数据与热数据在同一表,导致频繁全表扫描。
垂直分库 vs 水平分表
垂直分库是按业务领域拆,将不同模块的表放入不同数据库,是最容易理解的拆分。水平分表/分库则是按某个分片键将同一张表的数据分散到多个物理库/表,解决单表数据量过大。真正的Sharding通常指水平分库分表。
分片策略选择
- 取模方式:根据分片键(如用户ID)对库数量取模,分布均匀但扩缩容需迁移数据。
- 范围分片:按时间段或ID范围划分,例如1~10000在分片1、10001~20000在分片2。不涉及重新哈希,但可能数据倾斜。
- 一致性哈希:引入虚拟节点,减少扩缩容时数据迁移量。适合动态扩缩容场景。
中间件选型
当前主流分库分表中间件:
- ShardingSphere(JDBC + Proxy两套模式):支持读写分离、分库分表、分布式事务、弹性伸缩。社区活跃,文档完善。
- Vitess:由YouTube发展而来,Kubernetes原生部署,适合大规模云原生架构。
- TiDB:原生分布式数据库,对应用几乎无侵入,但内部实现是存储计算分离,属于另一条路线。
- TDSQL / OceanBase:商业方案,适合银行等强一致性场景。
演进中的坑与对策
分布式事务
分库后,原本单库的本地事务变成跨库事务。方案包括:
- XA两阶段提交:强一致但性能差,不适合高并发。
- TCC/柔性事务:最终一致,需业务补偿实现。
- BASE + 消息表:将分布式事务转为本地事务+可靠消息,实践中使用较多。
跨分片查询
全局查询(如按非分片键排序、聚合)需要中间件在所有分片执行后汇总,性能急剧下降。常见对策:
- 设计合理的分片键,使得大多数查询落在单个分片上。
- 使用全局索引表或ES/搜索引擎支撑复杂查询。
- 禁止跨分片JOIN,能在应用层聚合就在应用层完成。
全局主键
单库自增ID失效,需要全局唯一ID生成器:雪花算法、Redis INCR、Leaf(美团)等。注意时钟回拨问题。
数据迁移与双写
从单库或读写分离迁移到分库分表,必须做灰度双写:
- 写操作同时写入旧库和新库,新库按分片规则写入。
- 利用数据同步工具(如Canal)将旧库增量数据实时同步到新库。
- 全量校验与补偿:对比旧库和新库的数据,不一致的以旧库为准重写。
- 切换读流量:先切小部分读流量到新库,观察性能与正确性。
- 最终下掉旧库:确保新库稳定后,停止旧库写入并保留一段时间用于回滚。
落地路线图:从评估到回滚
第一步:明确当前瓶颈
收集监控指标:读QPS、写TPS、慢查询、连接数、磁盘IO。通过Performance Schema或慢日志定位最耗资源的SQL。如果90%压力来自读,读写分离即可;如果写和存储同时吃紧,考虑分库分表。
第二步:选择分片键与策略
分片键必须覆盖90%以上的核心查询场景。以订单表为例:按买家ID分片,买家查询自己订单落在同一分片;卖家查询则需建立卖家ID到买家ID的映射表或使用ES。
第三步:建设可观测性与灰度能力
部署链路追踪(如SkyWalking)和数据库监控。中间件ShardingSphere提供DistSQL动态调整分片规则,配合Apollo或Nacos做配置中心,实现规则变更不下线。
第四步:压测与回滚预案
在大规模切换前,必须进行全链路压测,模拟真实流量并验证分片均匀度、连接池大小、慢查询。同时预备回滚脚本:当新库出现性能恶化或数据错误,保留旧库完整服务能力,能在分钟级别切回。
演进不是终点,是常态
读写分离与分库分表都不是银弹。许多团队为了“架构先进”盲目拆分,反而陷入分布式事务、跨库查询的泥潭。正确的做法是:用量化指标驱动决策,用灰度机制控制风险,用最小可行拆分应对当前瓶颈。当业务继续增长,可能还需要引入分布式缓存、消息队列卸载写压力、或者直接使用NewSQL数据库。架构永远在演进,关键在于每一步都让团队可控、可测、可回滚。
延伸阅读
