分库分表怎么设计?分片键应该如何选择?
简化版
分库分表是把数据按分片键拆到多个表或库,用来解决单表过大、单机容量和写入压力问题。分片键要高频出现在查询条件中、分布均匀、业务边界清晰,并且能支撑扩容;选错分片键会导致跨分片查询、热点和迁移困难。
详细版
分库分表要先回答几个问题:
- 为什么拆:容量、写入、查询、归档还是组织边界?
- 按什么拆:用户 ID、商家 ID、订单 ID、时间还是组合策略?
- 怎么路由:应用层、代理层还是中间件?
- 跨分片查询怎么办:广播、聚合、冗余索引表还是搜索系统?
- 全局 ID 怎么生成:雪花 ID、号段、中心服务?
- 扩容怎么做:预分片、再哈希、迁移工具?
好的分片键一般具备:
- 查询经常带上;
- 数据和请求分布均匀;
- 不容易变化;
- 能隔离业务边界;
- 避免单分片热点。
完整版教学
一、分库分表是架构手术,不是普通优化
分库分表会改变数据访问方式。原来一条 SQL 能完成的查询,拆分后可能需要路由、聚合、排序、分页、事务补偿。
所以它通常是最后一类手段。能通过索引、冷热归档、读写分离、缓存、SQL 优化解决的问题,不要急着分库分表。
二、什么时候真的需要拆
常见信号:
- 单表数据量过大,DDL、备份、查询都越来越重;
- 单库写入吞吐达到瓶颈;
- 存储容量逼近单机上限;
- 热点业务和普通业务互相影响;
- 数据天然按租户、用户、商家隔离;
- 未来增长趋势明确超过单库承载能力。
如果只是某几条 SQL 慢,通常应该先查索引和 SQL,而不是拆库。
三、分片键决定系统命运
分片键是路由入口。比如订单按 user_id 分片,那么查某个用户订单很方便;但运营后台按商家、时间、状态查全站订单,就可能跨很多分片。
如果按 merchant_id 分片,商家后台方便,但用户维度查询可能变复杂。
没有完美分片键,只能围绕最核心、最高频、最不可妥协的访问路径选择。
四、常见分片策略
常见方式:
- 哈希取模:按
user_id % N分片,分布较均匀,但扩容迁移麻烦; - 范围分片:按时间或 ID 范围分片,便于归档,但可能有写热点;
- 预分片:提前规划较多逻辑分片,再映射到物理库;
- 组合分片:先按租户,再按时间或哈希拆。
选择策略时要同时考虑查询、写入、扩容和运维。
一个最简单的哈希路由公式是:
shard_no = hash(sharding_key) % shard_count
比如 user_id=10086、shard_count=16 时,路由到哪个分片完全由计算结果决定。这个公式看起来简单,但扩容到 32 个分片时,大量 key 的取模结果会变化,所以生产系统常用逻辑分片层降低迁移范围。
五、跨分片问题怎么处理
分库分表后,最痛的是跨分片:
- 跨分片 JOIN 很难;
- 全局排序分页复杂;
- 聚合统计成本高;
- 跨分片事务成本高;
- 唯一约束不再天然全局生效。
解决方式包括:冗余维度表、建立查询索引表、异步汇总、使用搜索引擎、OLAP 系统、最终一致事务方案。
六、扩容要提前设计
如果一开始按 user_id % 4 拆成 4 个库,后来要扩到 8 个,很多数据都要迁移。迁移期间还要处理双写、校验、回滚和流量切换。
更成熟的做法是引入逻辑分片层,比如 1024 个逻辑分片映射到少量物理库。扩容时移动一部分逻辑分片到新库,影响更可控。
分片前还要确认全局唯一约束怎么处理。单库里 unique(order_no) 很简单,拆成 32 个库后,如果订单号不是统一生成而是各库自增或各业务线自造,就可能出现跨分片重复。全局 ID、唯一索引表或发号服务要在拆分方案里提前设计。
七、常见误区与追问
| 问题 | 拆分前能否先尝试 | 拆分后必须补的能力 |
|---|---|---|
| 单条 SQL 慢 | 索引、执行计划、SQL 改写 | 分片路由也救不了坏 SQL |
| 单表过大 | 冷热归档、分区、历史表 | 路由、迁移、校验 |
| 单库写入瓶颈 | 批量写、削峰、缓存 | 全局 ID、跨分片事务方案 |
| 多维查询 | 搜索/OLAP/冗余索引表 | 跨分片聚合与分页 |
易错点:分片键一旦选定,就会进入所有查询、扩容和数据迁移路径。它不是字段命名问题,而是系统未来几年的访问形态问题。
举个热点例子:如果按 merchant_id 分片,一个头部商家占全站 30% 订单,那么这个商家所在分片会持续热点;即使总共有 32 个分片,也不是每个分片平均承压。分片键不仅要让数据量均匀,还要让请求量尽量均匀。
- 误区:表超过千万行就必须分库分表。 数据量只是信号之一,还要看行宽、索引、查询模式、写入压力、归档能力和硬件资源。
- 误区:分片后查询一定更快。 如果查询不带分片键,反而可能广播到所有分片再聚合,延迟和复杂度更高。
- 误区:哈希分片天然没有热点。 如果某个业务实体本身请求量极高,即使哈希均匀,也可能在单个实体所在分片形成热点。
- 追问:分片键怎么选? 优先选择高频查询必带、分布均匀、稳定不变、能表达业务边界的字段。
- 追问:跨分片分页为什么难? 每个分片都可能有候选数据,需要分别查询、全局归并排序,再计算下一页游标或偏移。
- 追问:扩容为什么建议逻辑分片? 例如 1024 个逻辑分片映射到 4 个物理库,扩到 8 个库时只迁移部分逻辑分片,不必让所有数据重新取模。
八、加强记忆
分库分表解决容量和吞吐,但代价是查询、事务、扩容和运维复杂度暴涨。分片键要跟最高频访问路径一致,并尽量均匀稳定;拆之前先确认普通优化已经不够,拆之后要有路由、全局 ID、跨分片查询和扩容方案。