07 - 怎么分:拆分策略
决定要分库分表后,最核心的问题是怎么拆。拆分方式选错,后面会非常痛苦。这一章讲两种拆分维度(垂直/水平)和最关键的决策——分片键怎么选。
一句话先记住:垂直拆是「按业务/字段切」,水平拆是「按行切」。水平拆里,选对「分片键」是重中之重。
7.1 垂直拆分:按「业务」或「字段」切
垂直分库:按业务拆
把不同业务的表,拆到不同的库:
原来:一个大库装所有表
┌────────── 单库 ──────────┐
│ 用户表 商品表 订单表 日志表 │
└──────────────────────────┘
垂直分库后:按业务拆开
┌用户库┐ ┌商品库┐ ┌订单库┐ ┌日志库┐
│用户表│ │商品表│ │订单表│ │日志表│
└─────┘ └─────┘ └─────┘ └─────┘
→ 各业务独立、互不影响,也是"微服务"数据库拆分的基础
垂直分表:按字段拆
把一张表的字段,拆成多张表(常用列 vs 不常用/大字段):
原来:users 表字段又多又有大字段
users(id, name, age, ..., avatar_blob, bio_text大字段)
垂直分表后:
users_base(id, name, age, ...) ← 常用,苗条,查得快
users_extra(id, avatar_blob, bio_text) ← 不常用/大字段,单独放
垂直拆分的特点:拆完,每个库/表的「行数」没变,变的是「字段/表的分布」。它解决的是「表太宽、业务耦合」,不解决「单表行数太多」。
7.2 水平拆分:按「行」切(重点)
水平拆分才是解决「单表行数太多」的核心手段。同一张表结构,把「行」按规则分到多张表/多个库:
user 表 5 亿行,水平拆成 4 份:
user_0 : 存一部分行
user_1 : 存一部分行
user_2 : 存一部分行
user_3 : 存一部分行
每张表结构完全一样,只是各存 1.25 亿行
关键问题:一行数据,到底该放进哪个分片?这由「分片键 + 分片算法」决定。
7.3 三种水平拆分算法
① 按范围(Range)
按 id 或时间范围分:
user_0 : id 1 ~ 1000万
user_1 : id 1000万+1 ~ 2000万
user_2 : id 2000万+1 ~ 3000万
✅ 优点:范围查询友好(查某段 id/某个月的数据,落在一个分片)
扩容简单(数据满了直接加新分片,老数据不用动)
❌ 缺点:容易"热点不均"——新数据都往最新的分片写,
最新分片是热点,老分片没人访问(冷热不均)
② 按哈希(Hash)
对分片键取哈希再取模:分片号 = hash(user_id) % 4
user_id=1001 → hash % 4 = 2 → 放 user_2
user_id=1002 → hash % 4 = 0 → 放 user_0
✅ 优点:数据分布均匀,没有热点
❌ 缺点:范围查询困难(相邻 id 被打散到各分片)
扩容痛苦——分片数从 4 变 8,取模结果全变,几乎所有数据要重新搬
为缓解哈希扩容难题,实际常用一致性哈希或预分片(一开始就分很多逻辑片,物理上先合并,扩容时再分开)。
③ 按时间
按时间分(其实是范围的一种特例,很常用):
order_2023, order_2024, order_2025 ...
✅ 适合:日志、订单这类"有明显时间属性、老数据少访问"的数据
老数据可以直接归档/删除整个分片
| 算法 | 优点 | 缺点 | 适合 |
|---|---|---|---|
| 范围 | 范围查询快、扩容易 | 冷热不均 | 有序、按段查 |
| 哈希 | 分布均匀 | 范围查询难、扩容痛 | 均匀打散的点查 |
| 时间 | 归档方便 | 冷热不均 | 日志、订单 |
7.4 分片键(Sharding Key)怎么选(最关键)
分片键 = 「决定一行数据放哪个分片」的那个字段。选错分片键,是分库分表最致命的错误,因为改起来极其痛苦。
选分片键的原则:
① 尽量让查询都带上分片键
→ 查询带分片键 → 直接定位到某个分片(快)
→ 查询不带分片键 → 得查所有分片再合并(慢,叫"广播查询")
例:用户系统用 user_id 做分片键
查"某用户的信息" → 带 user_id → 精准定位 ✅
查"某手机号的用户" → 不带 user_id → 要问遍所有分片 ❌
② 让数据分布均匀,避免热点
③ 选「几乎不会变」的字段
分片键一旦变了,数据就要从一个分片搬到另一个分片,极其麻烦
(所以别用"状态""地区"这种会变的字段做分片键)
经验:分片键要贴着「最高频的查询维度」选。用户中心用
user_id,订单系统常用user_id(保证「同一用户的订单在同一分片」,查我的订单很快)。
一个经典难题:多维度查询
订单表用 user_id 分片:
· 查"我的订单"(带 user_id)→ 快 ✅
· 但商家要查"我店铺的所有订单"(带 shop_id,不带 user_id)→ 要查所有分片 ❌
常见解法:
· 用另一份「按 shop_id 分片」的冗余数据(同一份数据存两套,各按不同键分片)
· 或把订单同步一份到 ES / 数据仓库,专门应对多维度查询
7.5 小结选型思路
先问:是业务耦合/表太宽? → 垂直拆(分业务库、拆宽表)
再问:是单表行数太多? → 水平拆
→ 按什么算法?范围(易扩容)/哈希(均匀)/时间(易归档)
→ 分片键选谁?贴着最高频查询维度、均匀、不变
本章小结
| 你要记住 | 要点 |
|---|---|
| 垂直拆 | 按业务/字段切,解决耦合和宽表,不减行数 |
| 水平拆 | 按行切,解决单表行数太多,核心手段 |
| 三种算法 | 范围(易扩容易热点)、哈希(均匀难扩容)、时间(易归档) |
| 分片键 | 最关键决策:贴高频查询维度、分布均匀、几乎不变 |
| 多维查询 | 分片键只优化一个维度,其他维度靠冗余/ES 解决 |
下一章预告:拆是拆了,但主键会冲突、跨库 JOIN 做不了、跨库事务怎么办……分库分表带来的一堆新问题,逐个来看。
上一章 ← 06 - 为什么要分库分表 | 下一章 → 08 - 分库分表带来的新问题