分库分表策略

我来详细讲解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

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

相关推荐
常利兵2 小时前
大文件上传不再卡顿:Spring Boot 分片上传、断点续传与进度条实现全解析
spring boot·后端·php
用户79140679683933 小时前
MySQL的索引类型
后端
楼田莉子3 小时前
同步/异步日志系统:日志器管理器模块\全局接口\性能测试
linux·服务器·开发语言·c++·后端·设计模式
geNE GENT3 小时前
Spring Boot管理用户数据
java·spring boot·后端
怒放吧德德3 小时前
Spring Boot实战:Event事件机制解析与实战
java·spring boot·后端
梦无矶3 小时前
快速设置uv默认源为国内镜像
数据库·redis·后端·python·uv
yoyo_zzm4 小时前
SpringBoot Test详解
spring boot·后端·log4j
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】01_说说ArrayList的底层原理/扩容规则
java·后端·面试·list