2026年7月13日 · 4 分钟阅读
分库分表实战:拆分策略、在线迁移、动态扩容与分布式 ID
从容量与并发瓶颈出发,系统梳理垂直拆分、水平分片、路由键、跨分片限制、在线双写迁移、扩缩容和分布式 ID 设计。
分库分表不是数据库变慢后的第一选择。索引、SQL、表结构、归档、缓存和读写分离都应先评估;只有单机容量、写吞吐或维护窗口确实成为瓶颈,才值得承担分片复杂度。
为什么拆分
- 单实例存储、IO、连接或写吞吐接近安全水位。
- 单表工作集和索引过大,备份、DDL、归档越来越困难。
- 不同业务域需要独立扩展、发布和故障隔离。
不存在通用的“超过几百万行必须分表”。是否拆分取决于行宽、索引、查询模式、硬件、SLO 和增长速度,应以压测与容量预测为依据。
垂直拆分与水平分片
垂直拆分按业务或字段拆:订单、支付、商品各自成库;宽表也可将低频大字段拆走。它改善边界和缓存效率,但跨域 Join 会变成 RPC 或离线聚合。
水平分片保持表结构相同,把不同记录放到不同节点:
| 策略 | 优点 | 风险 |
|---|---|---|
hash(user_id) % N | 数据和流量较均匀 | 扩容时大量数据重映射 |
| 范围/时间 | 路由和归档简单 | 新分片热点明显 |
| 目录表 | 路由灵活、易迁移单租户 | 目录服务成为关键依赖 |
| 虚拟槽 | 迁移槽而非修改业务规则 | 需要维护槽到节点映射 |
路由键决定上限
路由键应让最常见查询能定位单分片,同时分布均匀且长期稳定。按 user_id 分片适合用户中心,却可能让“按订单号查询”需要额外映射;按 order_id 分片又不利于用户订单列表。
常用办法是冗余路由字段、建立全局索引表、搜索引擎或分析库承载跨分片查询。跨分片分页、排序、Join、唯一约束和事务都会更昂贵,设计阶段必须列出这些查询。
中间件怎么选
客户端模式在应用内完成路由,链路短、性能高,但升级需要所有应用发布。Proxy 模式对语言和应用透明,治理集中,但多一跳并需要独立高可用运维。当前可评估 Apache ShardingSphere 的 JDBC 与 Proxy 形态,不能沿用旧资料对多年未维护组件的结论。
在线迁移流程
可靠迁移不是简单双写,而是可校验、可回滚的状态机:
准备新分片与路由
→ 全量回填历史数据
→ CDC/binlog 同步增量
→ 按主键、数量、校验和持续对账
→ 灰度影子读新库
→ 分批切读
→ 切写并保留回退窗口
→ 停止旧链路并归档
应用双写会遇到一边成功一边失败、顺序颠倒和重试重复。更稳妥的方案通常以旧库为阶段性事实来源,通过 Outbox/CDC 幂等同步新库。回填必须按版本或更新时间比较,防止历史数据覆盖新值。
动态扩缩容
直接从 % 4 改成 % 8 会重映射大量数据。虚拟槽把逻辑路由与物理节点解耦:例如先定义 1024 个槽,扩容只迁移部分槽并更新映射。
迁移单元经历“复制—追增量—校验—切路由—观察—清旧数据”。切换期间请求可双读或由目录表指向权威位置。必须限速迁移,避免挤占线上数据库 IO。
分布式 ID
| 方案 | 特点 | 适用场景 |
|---|---|---|
| UUID | 无中心、生成简单 | 不要求趋势递增,注意索引局部性 |
| 数据库号段 | 批量取号、趋势递增 | 能接受号段服务和少量跳号 |
| Snowflake 类 | 时间戳 + 节点 + 序列,本地高吞吐 | 需要管理节点号和时钟回拨 |
| Redis INCR | 简单递增 | Redis 成为关键依赖,需评估持久化与切换 |
ID 需要全局唯一,但不一定连续。不要把分片号直接暴露成不可演进的业务规则;若编码时间或节点信息,要处理时钟回拨、隐私和容量边界。
工程检查清单
- 有压测和增长数据证明必须拆分。
- 80% 以上核心请求能由路由键定位单分片。
- 跨分片查询、唯一约束和事务有明确替代方案。
- 迁移支持幂等、限速、对账、灰度与回滚。
- 扩容使用虚拟槽或目录映射,避免全量停机重哈希。
- ID 服务有容量、时钟和故障演练。
面试回答要点
先说明分表解决单表维护与查询压力,分库解决单实例容量和吞吐;再讲路由键、hash/range 取舍和跨分片代价。迁移按全量、增量、对账、灰度、切换、回滚展开,最后补虚拟槽扩容和分布式 ID。
总结
分片把单机问题变成分布式数据治理问题。真正困难的不是取模,而是路由键、跨分片能力、在线迁移、对账回滚和未来扩容。没有这些闭环,分库分表只是在提前制造复杂度。