首页 / 知识库 / python服务端进阶 / 数据库优化-索引与分库分表

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 - 分库分表带来的新问题