分库分表策略

我来详细讲解MySQL分库分表的策略、实现方式以及核心难点,这是高并发系统设计的必考点。


一、为什么需要分库分表

css 复制代码
┌─────────────────────────────────────────┐
│  单表瓶颈(InnoDB)                        │
│  - 数据量>5000万行,B+Tree高度增加,IO上升   │
│  - 单表文件>10GB,备份/DDL(加索引)耗时数小时 │
│  - 单库连接数上限(默认151,max_connections) │
│  - 写瓶颈:单机CPU/磁盘IO有上限             │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  分库分表目标                              │
│  1. 分散存储压力(数据水平拆分)             │
│  2. 分散访问压力(读写流量分散到多实例)      │
│  3. 突破单机连接数限制                      │
└─────────────────────────────────────────┘

二、分表 vs 分库 vs 分库分表

方案 定义 解决问题 复杂度
分表 单库内,表拆分为多张 单表数据量过大、索引膨胀
分库 数据分散到多个数据库实例 单库连接数瓶颈、写性能上限
分库分表 先分库,库内再分表 数据量+连接数双重瓶颈

三、分表策略(单库内)

1. 垂直分表(按列拆分)

bash 复制代码
┌─────────────────────────────┐         ┌─────────────────────────────┐
│         user表(宽表)         │         │      user_basic(热数据)    │
│  id, name, avatar,          │   →    │  id, name, avatar, phone     │
│  phone, address, bio,        │         │  (频繁查询,常驻内存)        │
│  login_log, order_history    │         └─────────────────────────────┘
│  (100+字段,行大小>4KB)      │         ┌─────────────────────────────┐
└─────────────────────────────┘         │      user_extra(冷数据)      │
                                         │  id, address, bio, login_log │
                                         │  (偶尔查询,可存SSD或归档)    │
                                         └─────────────────────────────┘

原则 :将访问频率不同数据大小差异大的字段分离。

  • 热数据行小,Buffer Pool命中率高
  • 冷数据可压缩存储,甚至归档到HBase/ClickHouse

2. 水平分表(按行拆分)

markdown 复制代码
┌─────────────────────────────┐
│      user表(1000万行)       │
└─────────────────────────────┘
           ↓ 按 user_id % 4
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│user_000│ │user_001│ │user_002│ │user_003│
│ 0-249w │ │250-499w│ │500-749w│ │750-999w│
└────────┘ └────────┘ └────────┘ └────────┘

分片键选择user_id(查询维度最频繁的字段)


四、分库策略(多实例)

1. 垂直分库(按业务域拆分)

scss 复制代码
┌─────────────────────────────────────────┐
│           单体数据库(所有业务)            │
│  user | order | product | payment | log  │
└─────────────────────────────────────────┘
                    ↓
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│  user-db    │ │  order-db   │ │  product-db │
│  (用户核心)  │ │  (订单交易)  │ │  (商品库存)  │
│  主从复制    │ │  主从复制    │ │  主从复制    │
└─────────────┘ └─────────────┘ └─────────────┘

特点

  • 业务解耦,独立扩容
  • 跨库Join需应用层组装(或用宽表/ES冗余)
  • 分布式事务问题(后文详述)

2. 水平分库(同业务数据分散)

scss 复制代码
┌─────────────────────────────────────────┐
│         order-db(单机瓶颈)              │
│      日增100万订单,写入压力过大           │
└─────────────────────────────────────────┘
                    ↓ 按 user_id % 4
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ order-db-00 │ │ order-db-01 │ │ order-db-02 │ │ order-db-03 │
│  user_id%4=0│ │  user_id%4=1│ │  user_id%4=2│ │  user_id%4=3│
│  (0,4,8...) │ │  (1,5,9...) │ │  (2,6,10..) │ │  (3,7,11..) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘

五、分片算法(Sharding Algorithm)

1. 取模/哈希分片

java 复制代码
// 简单取模
int dbIndex = userId % 4;        // 分4库
int tableIndex = (userId / 4) % 8; // 每库8表,共32张表

// 一致性哈希(解决扩容时数据迁移问题)
// 节点增减只影响相邻区间,无需全量迁移

优点 :数据分布均匀,点查高效
缺点:扩容时数据迁移量大(需rehash),范围查询需扫所有分片


2. 范围分片

java 复制代码
// 按时间范围(适合时序数据)
db_2024_q1: 2024-01-01 ~ 2024-03-31
db_2024_q2: 2024-04-01 ~ 2024-06-30

// 按ID范围
db_0: user_id 0 ~ 1000万
db_1: user_id 1000万 ~ 2000万

优点 :扩容简单(新增区间即可),范围查询友好
缺点:热点问题(新数据集中在最新分片),需配合读写分离


3. 混合分片(推荐)

java 复制代码
// 先范围确定库,再取模确定表
// 避免取模扩容迁移,又避免范围热点

// 步骤1:按时间范围选库(如2024年数据在db_2024)
String db = "db_" + year;

// 步骤2:库内按user_id % 1024 分表
int table = (userId % 1024);

适用:日志、订单等时间敏感型数据。


4. 分片算法对比

算法 数据分布 扩容迁移 范围查询 适用场景
取模 均匀 大(全量rehash) 用户ID等离散键
范围 可能不均 无(新增区间) 时间序列、日志
一致性哈希 较均匀 小(相邻节点) 缓存分片(Redis)
混合 可控 中等 中等 订单、交易流水

六、分库分表实现方式

方式1:客户端Sharding(应用层)

java 复制代码
// 使用ShardingSphere-JDBC(轻量,无中间件依赖)
@Configuration
public class ShardingConfig {
    
    @Bean
    public DataSource shardingDataSource() throws SQLException {
        // 配置分片规则
        ShardingRuleConfiguration rule = new ShardingRuleConfiguration();
        
        // 分库策略:user_id取模
        rule.setDefaultDatabaseShardingStrategy(
            new StandardShardingStrategyConfiguration(
                "user_id", 
                new InlineShardingStrategyConfiguration("ds_${user_id % 4}")
            )
        );
        
        // 分表策略:每库8表
        rule.setDefaultTableShardingStrategy(
            new StandardShardingStrategyConfiguration(
                "user_id",
                new InlineShardingStrategyConfiguration("user_${user_id % 32}")
            )
        );
        
        return ShardingSphereDataSourceFactory.createDataSource(
            createDataSourceMap(), 
            Collections.singleton(rule), 
            new Properties()
        );
    }
}

优点 :性能损耗低(直接路由到真实数据源),无单点
缺点:配置侵入代码,语言绑定(Java)


方式2:代理中间件(Proxy)

scss 复制代码
┌─────────┐     ┌─────────────┐     ┌─────────┐ ┌─────────┐
│ 应用    │────→│  Sharding   │────→│ MySQL-0 │ │ MySQL-1 │
│ (任意语言)│     │  -Proxy     │     │  主从   │ │  主从   │
└─────────┘     │ (独立进程)   │     └─────────┘ └─────────┘
                └─────────────┘
                 自动解析SQL,路由改写

优点 :对应用透明,语言无关,集中管控
缺点:多一层网络跳转(延迟+~1ms),Proxy本身高可用需保障

代表产品:ShardingSphere-Proxy、MyCat、Vitess


方式3:云原生/数据库中间件(NewSQL)

sql 复制代码
-- TiDB/OceanBase 自动分片,应用无感知
CREATE TABLE user (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50)
) ENGINE=InnoDB
PARTITION BY HASH(id) PARTITIONS 16;  -- 自动分布式

-- 应用像访问单机MySQL一样访问TiDB

优点 :完全透明,自动扩缩容,强一致分布式事务
缺点:成熟度、生态、运维复杂度


七、核心难点与解决方案

难点1:分布式主键(全局唯一ID)

java 复制代码
// 方案1:雪花算法(Snowflake)- 推荐
// 41位时间戳 + 10位机器ID + 12位序列号 = 64位Long
// 优点:趋势递增,插入性能高(避免B+Tree频繁分裂)
// 缺点:依赖时钟,时钟回拨会重复(需NTP校准或等待)

// 方案2:号段模式(Leaf)
// 从DB批量获取ID区间(如[1000,2000)),内存分配
// 优点:性能极高,无时钟依赖
// 缺点:需额外服务(美团Leaf)

// 方案3:数据库自增 + 步长
// DB-0: 1, 5, 9... (step=4, offset=1)
// DB-1: 2, 6, 10... (step=4, offset=2)
// 缺点:扩展困难,非连续

难点2:跨分片查询/聚合

sql 复制代码
-- 场景:查询所有用户的订单总额(需聚合4库8表共32张表)
SELECT user_id, SUM(amount) FROM order GROUP BY user_id;

-- 方案1:应用层聚合(ShardingSphere自动处理)
// Proxy层并行查询各分片,内存归并结果
// 缺点:大数据量时内存压力大,延迟高

-- 方案2:异构索引表(冗余存储)
// 将聚合结果实时同步到ClickHouse/ES
// 查询走OLAP引擎,原始数据走MySQL分片

-- 方案3:限制查询维度
// 强制带分片键:WHERE user_id = ?(点查)
// 禁止无分片键的全局扫描(或走离线数仓)

难点3:分布式事务

java 复制代码
// 场景:下单扣库存,跨order-db和inventory-db
// 方案1:最终一致性(Saga/TCC)- 互联网主流
@Compensable(confirmMethod = "confirm", cancelMethod = "cancel")
public void createOrder(Order order) {
    orderService.save(order);        // 本地事务
    inventoryService.deduct(order);    // RPC调用,可能失败
}
// 失败时执行cancel:回滚订单 + 释放库存

// 方案2:Seata AT模式(自动补偿)
// 代理数据源,解析SQL生成Undo Log,全局协调器驱动二阶段提交

// 方案3:XA协议(强一致,性能差)
// 两阶段提交,阻塞协议,很少用于高并发场景

难点4:数据迁移与扩容

markdown 复制代码
平滑扩容流程(取模→取模,如4库扩8库):

1. 双写阶段(2周)
   应用层同时写旧分片(4库)和新分片(8库)
   读走旧分片(保证一致性)

2. 历史数据迁移
   脚本将旧4库数据rehash到新8库
   对比校验(MD5/行数)

3. 切读阶段
   灰度将读流量切到新8库
   观察一周,比对数据一致性

4. 停写旧库
   关闭双写,只写新8库
   保留旧库备份,1月后清理

八、分库分表设计 checklist

yaml 复制代码
1. 是否真的需要?
   - 数据量<1000万?先优化索引/SQL,加缓存,读写分离
   - QPS<1万?单机+主从足够

2. 分片键选择
   - 查询维度最多的字段(如user_id)
   - 避免跨分片查询(如按user_id分片,但大量查询按time_range)

3. 分几片?
   - 预估3-5年数据量,取模数选2^n(方便后续分裂)
   - 示例:当前500万/年,预期5年2500万,分32片(每片~80万,健康)

4. 全局表(小数据量配置表)
   - 每个库冗余一份,避免跨库Join
   - 或放分布式缓存(Redis)

5. 非分片键查询
   - 建立异构索引表(如按手机号查询,需额外维护mobile→user_id映射)
   - 或同步到Elasticsearch

九、高频面试追问

Q:分库分表后如何分页查询?

sql 复制代码
-- 场景:ORDER BY time LIMIT 100000, 10(深分页)
-- 问题:需每个分片查100010条,内存归并排序,性能极差

-- 方案1:禁止深分页,产品层限制(只能前100页)
-- 方案2:游标分页(上次最后一条的time作为起点)
WHERE time < '2024-01-01 12:00:00' ORDER BY time DESC LIMIT 10;
-- 方案3:聚合到ES/ClickHouse,分库分表只支持点查

Q:分库分表后Join怎么办?

java 复制代码
// 同库Join:ShardingSphere支持(如果关联表分片策略相同)
// 跨库Join:应用层组装,或宽表冗余

// 反范式设计(空间换时间)
// 订单表冗余用户姓名、商品名称,避免Join
CREATE TABLE order (
    order_id BIGINT,
    user_id BIGINT,
    user_name VARCHAR(50),    -- 冗余,避免查user表
    product_id BIGINT,
    product_name VARCHAR(100), -- 冗余
    ...
);

Q:分库分表和NewSQL(TiDB)怎么选?

维度 分库分表+Proxy NewSQL(TiDB)
成熟度 高,生态完善 中,快速迭代
运维成本 高(自研Proxy、扩容脚本) 低(自动化)
性能 接近原生MySQL ~70%原生MySQL(分布式事务开销)
复杂查询 受限(跨分片性能差) 友好(自动分布式执行计划)
团队规模 需DBA+中间件专家 可少配DBA

建议

  • 已有成熟MySQL生态,团队强 → 分库分表
  • 新业务,团队小,想快速上线 → TiDB/OceanBase

分库分表是**"最后一招"**,在此之前应穷尽优化手段(索引、缓存、读写分离、归档)。一旦拆分,架构复杂度不可逆上升,需配套完善的运维体系和监控能力。

相关推荐
颜酱8 分钟前
# 02 | 搭骨架:用 LangGraph 编排 12 步工作流(思路)
前端·人工智能·后端
颜酱9 分钟前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑1 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康1 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端
我叫黑大帅2 小时前
我为什么单一消费者的场景下,要用 Redis List 当消息队列?
redis·后端·面试
AskHarries2 小时前
文件上传系统
后端
止语Lab3 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活3 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困3 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn3 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端