Skip to content

04 - 分库分表

为什么要分库分表?

单库单表的瓶颈:

  ┌─────────────────────────────────────────────────────┐
  │  单表数据量               性能表现                    │
  │  ─────────────           ─────────                  │
  │  < 500万行               查询很快                    │
  │  500万 ~ 2000万          开始变慢(需要优化索引)      │
  │  > 2000万                明显变慢(考虑分表)          │
  │  > 5000万                很慢(必须分表)              │
  │  > 1亿                   不分不行了!                  │
  │                                                     │
  │  单库连接数               性能表现                    │
  │  ─────────────           ─────────                  │
  │  < 1000 并发连接          正常                       │
  │  > 3000 并发连接          需要分库                    │
  └─────────────────────────────────────────────────────┘

1. 拆分策略

┌────────────────────────────────────────────────────────────┐
│                                                            │
│  垂直拆分 (按业务/字段)          水平拆分 (按数据)           │
│                                                            │
│  拆分前:                        拆分前:                     │
│  ┌──────────────┐              ┌──────────────┐            │
│  │ 大数据库      │              │ order 表     │            │
│  │              │              │ 1亿行!       │            │
│  │ user 表      │              └──────────────┘            │
│  │ order 表     │                                          │
│  │ product 表   │              拆分后:                     │
│  │ payment 表   │              ┌───────────┐┌───────────┐  │
│  └──────────────┘              │ order_00  ││ order_01  │  │
│                                │ 2500万行   ││ 2500万行   │  │
│  拆分后:                        └───────────┘└───────────┘  │
│  ┌────────┐┌────────┐          ┌───────────┐┌───────────┐  │
│  │用户库   ││商品库   │          │ order_02  ││ order_03  │  │
│  │user表  ││product │          │ 2500万行   ││ 2500万行   │  │
│  └────────┘└────────┘          └───────────┘└───────────┘  │
│  ┌────────┐┌────────┐                                      │
│  │订单库   ││支付库   │          总计 4 张表,每表 2500 万     │
│  │order表 ││payment │                                      │
│  └────────┘└────────┘                                      │
│                                                            │
│  何时用:                        何时用:                     │
│  表太多/字段太多                 单表数据量太大               │
│  不同业务解耦                    读写压力太大                 │
│                                                            │
└────────────────────────────────────────────────────────────┘

2. 分片策略

常见分片键选择和路由算法:

  1. Hash 取模:
     table_index = hash(user_id) % 4

     user_id=1001 → hash=xxx → xxx % 4 = 1 → order_01
     user_id=1002 → hash=xxx → xxx % 4 = 2 → order_02

     ┌────────────┬────────────┬────────────┬────────────┐
     │ order_00   │ order_01   │ order_02   │ order_03   │
     │ id%4==0    │ id%4==1    │ id%4==2    │ id%4==3    │
     └────────────┴────────────┴────────────┴────────────┘

     优点: 数据均匀
     缺点: 扩容需要迁移大量数据

  2. Range 范围:
     user_id 1~1000万    → order_00
     user_id 1000万~2000万 → order_01

     优点: 扩容简单(加新表)
     缺点: 热点问题(新用户都在最后一张表)

  3. 一致性 Hash:

       0 ───── Node A
      ╱                ╲
    Node D              Node B
      ╲                ╱
       ──── Node C ────

     数据落在顺时针方向最近的节点
     优点: 扩容只迁移部分数据
     缺点: 实现复杂

  推荐:
  ┌──────────────────────────────────────────────┐
  │  大多数场景: Hash 取模 + 预分足够多的表          │
  │  比如一开始就分 64 或 128 张表                  │
  │  初期多个逻辑表可以在同一个物理库               │
  │  后期扩容只需要迁移部分库                       │
  └──────────────────────────────────────────────┘

3. 分布式 ID 生成

分库分表后自增ID不能用了!需要全局唯一ID

  ┌────────────────────────────────────────────────────────┐
  │  方案              │ 特点                               │
  ├────────────────────┼───────────────────────────────────┤
  │  UUID              │ 简单,但无序、太长、索引效率差       │
  │  数据库自增步长     │ 简单,但依赖DB、有上限              │
  │  Redis INCR        │ 简单高性能,但依赖Redis             │
  │  Snowflake雪花算法 │ 有序、高性能、不依赖外部 ✓          │
  └────────────────────┴───────────────────────────────────┘

  Snowflake ID 结构 (64 bit):
  ┌──────────────────────────────────────────────────────────┐
  │ 0 │ 41 bit 时间戳      │ 10 bit 机器ID │ 12 bit 序列号  │
  │   │ (69年不重复)       │ (1024台机器)   │ (每ms 4096个) │
  └──────────────────────────────────────────────────────────┘

  ▶ 每台机器每毫秒可生成 4096 个 ID
  ▶ 趋势递增,对 B+ 树索引友好
  ▶ 不依赖任何外部服务

Go 实现简化版 Snowflake

go
package main

import (
    "fmt"
    "sync"
    "time"
)

const (
    epoch         = 1700000000000 // 自定义纪元 (2023-11-14)
    machineBits   = 10
    sequenceBits  = 12
    maxMachineID  = (1 << machineBits) - 1   // 1023
    maxSequence   = (1 << sequenceBits) - 1   // 4095
    machineShift  = sequenceBits              // 12
    timestampShift = machineBits + sequenceBits // 22
)

type Snowflake struct {
    mu        sync.Mutex
    machineID int64
    sequence  int64
    lastTime  int64
}

func NewSnowflake(machineID int64) *Snowflake {
    if machineID > maxMachineID {
        panic("machine ID out of range")
    }
    return &Snowflake{machineID: machineID}
}

func (s *Snowflake) Generate() int64 {
    s.mu.Lock()
    defer s.mu.Unlock()

    now := time.Now().UnixMilli() - epoch

    if now == s.lastTime {
        s.sequence = (s.sequence + 1) & maxSequence
        if s.sequence == 0 {
            // 当前毫秒序列号用完,等下一毫秒
            for now <= s.lastTime {
                now = time.Now().UnixMilli() - epoch
            }
        }
    } else {
        s.sequence = 0
    }

    s.lastTime = now

    return (now << timestampShift) |
        (s.machineID << machineShift) |
        s.sequence
}

func main() {
    sf := NewSnowflake(1)

    for i := 0; i < 10; i++ {
        id := sf.Generate()
        fmt.Printf("ID: %d\n", id)
    }
}

4. 小结

┌────────────────────────────────────────────────────────────┐
│  分库分表速查                                               │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  拆分策略:                                                  │
│  ├── 垂直拆分: 按业务拆库(用户库、订单库、支付库)          │
│  └── 水平拆分: 按数据拆表(order_00 ~ order_63)            │
│                                                            │
│  分片路由:                                                  │
│  ├── Hash取模: 数据均匀,最常用                             │
│  ├── Range:    扩容简单,有热点问题                         │
│  └── 一致性Hash: 扩容友好,实现复杂                         │
│                                                            │
│  分布式ID:  Snowflake 雪花算法(有序 + 高性能 + 无依赖)    │
│                                                            │
│  注意事项:                                                  │
│  ├── 跨表 JOIN → 冗余字段 / 应用层聚合                     │
│  ├── 跨表事务 → 分布式事务 / 最终一致性                     │
│  ├── 全局排序 → 归并排序 / 搜索引擎(ES)                    │
│  └── 能不分就不分! 先优化索引/缓存/读写分离                  │
│                                                            │
└────────────────────────────────────────────────────────────┘

模块三完成!

下一个模块: 模块四:AI Agent 架构