为什么需要分库分表?
当单机数据库(如 MySQL)面临以下瓶颈时,就需要考虑分库分表:
数据量过大(单表超千万/亿级,查询变慢、索引膨胀)
并发量过高(连接数打满、CPU/IO 负载高)
业务扩展需求(不同业务模块希望独立部署、隔离资源)
高可用与容灾要求(避免单点故障)
举例:一个电商订单表,每天新增百万订单,3 年后达到 10 亿+数据,单表查询性能急剧下降,此时必须分库分表。
垂直分库 vs 垂直分表 vs 水平分库 vs 水平分表
垂直分表(Vertical Partitioning)
定义:按“列”拆分,把一个表中不常用的字段或大字段(如 text、blob)拆到另一个表。
目的:减少单行数据大小,提升缓存命中率、减少 IO。
示例:
| |
适用场景:字段多、部分字段访问频率差异大、存在大字段。
垂直分库(Vertical Sharding)
定义:按“业务模块”拆分数据库,不同业务使用不同数据库实例。
目的:业务解耦、资源隔离、便于独立扩展。
示例:
- 用户库:user_db(用户、权限、积分)
- 订单库:order_db(订单、支付、物流)
- 商品库:product_db(商品、库存、类目)
适用场景:微服务架构下,不同服务拥有独立数据库。
注意:跨库 JOIN、事务需特殊处理(如分布式事务、应用层聚合)。
水平分表(Horizontal Partitioning)
定义:按“行”拆分,将同一个表的数据按某种规则分散到多个结构相同的表中。
目的:解决单表数据量过大问题。
示例:
| |
适用场景:单表记录数超千万,写入/查询性能下降。
水平分库(Horizontal Sharding)
定义:在水平分表基础上,将不同分片表分布到不同数据库实例中。
目的:同时解决“数据量大”和“并发高”的问题。
示例:
- 库实例 1:db0 → user_0, user_1, user_2
- 库实例 2:db1 → user_3, user_4, user_5
- …
适用场景:高并发 + 大数据量,如 C 端用户系统、订单系统、日志系统。
如何选择分片键(Sharding Key)?
分片键的选择直接决定分库分表效果,是核心设计点!
好的分片键特征:
- 高基数:值分布广,避免数据倾斜(如用户 ID、订单 ID)
- 查询高频:大部分查询都带分片键,避免全分片扫描
- 稳定不变:分片后不能改,否则需数据迁移(如用户 ID 比手机号更稳定)
- 业务相关:尽量贴近业务访问模式(如“按卖家 ID 分”适合卖家后台,“按买家 ID 分”适合买家 C 端)
差的分片键:
- 时间戳(导致写入集中在最新分片)
- 性别、状态等低基数字段(数据严重倾斜)
- 随机 UUID(无法范围查询,难管理)
常见策略:
- 哈希取模:
hash(sharding_key) % N→ 均匀分布,但扩容困难 - 范围分片:如 user_id [0-1000 万] → 分片 0,[1000 万-2000 万] → 分片 1 → 便于范围查询,但易热点
- 一致性哈希:扩容时只影响局部数据,适合动态扩缩容
- 复合分片:如“城市 ID + 用户 ID”,适合地域化部署
分库分表带来的问题 & 解决方案
| 问题 | 解决方案 |
|---|---|
| 跨分片查询(如按非分片键查) | ① 业务层避免;② 建“广播表”;③ 用 Elasticsearch 做二级索引;④ 全分片扫描(慎用) |
| 跨分片 JOIN | 应用层聚合、冗余字段、异步宽表、用数据中台统一处理 |
| 分布式事务 | ① 最终一致性(消息队列补偿);② Seata/TCC;③ 避免跨库事务,业务拆分 |
| 全局唯一 ID | Snowflake、Redis 自增、数据库号段模式、Leaf(美团)、Tinyid(滴滴) |
| 分页排序 | ① 业务限制(如只查最近 N 条);② 先查各分片 TopK 再合并;③ 用 Elasticsearch 辅助 |
| 扩容迁移 | 双写迁移、一致性校验、流量灰度切换(如 ShardingSphere-Scaling) |