主题
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 架构