分库分表策略

面试官问:"单表数据量太大怎么办?"

小张说:"分库分表。"

面试官追问:"怎么分?按用户 ID 分还是按时间分?"

小张说:"按用户 ID 分?"

面试官继续追问:"如果按时间查订单呢?比如查某个时间段的订单?"

小张开始答不上来。

分库分表是 MySQL 应对海量数据的高频方案。但这不只是一道"拆成多张表"那么简单。分片键怎么选、跨分片怎么查、数据怎么迁移——每一个都是坑。这道题能答清楚的候选人,对数据库架构的理解已经到了架构师级别。

一、分库分表的背景 🔴

1.1 单表数据量的问题

数据量问题
100 万行基本没问题
1000 万行索引查询仍然快,但备份时间长
5000 万行性能开始下降,DDL 操作风险增加
1 亿行严重性能问题,索引维护成本高
5 亿行单表无法承受,必须分表

核心原因:B+ 树索引在数据量大时,索引树层数增加,查询变慢。DML 操作维护索引成本增加。

1.2 分库 vs 分表

类型作用适用场景
垂直分库按业务拆分到不同数据库微服务架构、库级别拆分
垂直分表按字段拆分为多张表大字段多、冷热字段分离
水平分库按行拆分到不同数据库单库写入瓶颈
水平分表按行拆分到多张表单表数据量瓶颈

1.3 ❌ 错误示范

候选人原话:"数据量大了就分库分表,简单。"

问题诊断:分库分表是最后的手段,引入复杂度巨大。先考虑优化索引、归档历史数据、分区表等方案。

候选人原话 2:"分库分表后就高枕无忧了。"

问题诊断:分库分表引入了一系列新问题:跨分片查询、分片键选择、分布式 ID、分片扩缩容等。

【面试官心理】 这道题我会从分片键选择切入,这是分库分表最核心的问题。如果候选人能说出不同分片键的优缺点,说明他有实战经验。

二、分片策略 🔴

2.1 哈希分片

-- 按 user_id % 4 = 分片号
-- 分片 0: user_id = 4, 8, 12, ...
-- 分片 1: user_id = 1, 5, 9, ...
-- 分片 2: user_id = 2, 6, 10, ...
-- 分片 3: user_id = 3, 7, 11, ...

优点

  • 数据分布均匀
  • 写入分散到各个分片

缺点

  • 按范围查询困难(查 user_id 1~100 需要跨所有分片)
  • 扩容困难(需要重新哈希迁移数据)

2.2 范围分片

-- 按 id 范围分
-- 分片 0: id = 1 ~ 1000 万
-- 分片 1: id = 1000 万 ~ 2000 万
-- 分片 2: id = 2000 万 ~ 3000 万

优点

  • 按范围查询高效
  • 扩缩容简单(只影响相邻分片)

缺点

  • 数据可能不均匀(热点用户集中在某个范围)
  • 写入热点(新建订单集中在最后一个分片)

2.3 时间分片

-- 按时间分表
-- orders_202401: 2024 年 1 月订单
-- orders_202402: 2024 年 2 月订单
-- orders_202403: 2024 年 3 月订单

优点

  • 历史数据归档简单
  • 按时间查询高效

缺点

  • 最新分片写入热点
  • 需要定期新建分表

三、分片键选择 🟡

3.1 常见分片键

分片键适用场景不适用场景
user_id用户相关查询按时间范围查询订单
order_no订单号精确查询按用户查订单
created_at按时间范围查询按用户查所有订单
region_id按地区查询跨地区聚合查询

3.2 关联查询问题

-- 按 user_id 分片的 orders 表
-- 按 order_id 分片的 order_items 表

-- 查询用户的所有订单及其明细
SELECT o.*, i.*
FROM orders o
JOIN order_items i ON o.id = i.order_id
WHERE o.user_id = 1;
-- 问题:orders 和 order_items 分片键不同,无法 JOIN!

解决方案

  1. 冗余字段:在 order_items 表冗余 user_id,按 user_id 分片
  2. 多次查询:先查 orders,再批量查 order_items
  3. ES:把数据同步到 ES,用 ES 做关联查询

3.3 分片键选择原则

  1. 高频查询优先:选择查询频率最高的字段作为分片键
  2. 避免跨分片:尽量让关联查询在同一个分片内完成
  3. 数据均匀:分片键的值分布要均匀
  4. 业务解耦:分片键要稳定,避免分片键变更导致大量数据迁移

四、跨分片查询 🟡

4.1 聚合查询

-- 场景:查询所有分片的总订单数
SELECT COUNT(*) FROM orders;

-- 实现方式:
-- 1. 并行查询所有分片
-- 2. 汇总结果

-- 代码逻辑:
Future<List<Long>> futures = new ArrayList<>();
for (ShardingNode node : nodes) {
    futures.add(executor.submit(() -> {
        return jdbc.query(node, "SELECT COUNT(*) FROM orders");
    }));
}
Long total = futures.stream().mapToLong(f -> f.get()).sum();

4.2 分页查询

-- 场景:订单列表分页,每页 10 条
-- 问题:数据分散在多个分片

-- 解决方案:多次查询 + 内存分页
-- 1. 从每个分片查 100 条
-- 2. 在应用层按时间排序
-- 3. 取第 20~30 条

-- 缺点:深度分页性能差

4.3 分布式 ID

-- 分库分表后需要分布式 ID
-- 方案:
-- 1. 雪花算法 (Snowflake):时间 + 机器 ID + 序列号
-- 2. UUID:简单但无序
-- 3. 数据库号段:预分配 ID 区间
-- 4. 滴滴(TinyID):号段 + 双 Buffer

五、扩缩容 🟢

5.1 一致性哈希

-- 传统哈希:node = hash(key) % N
-- 一致性哈希:node = hash(key) % (N * M),M 是每个节点的分片倍数

5.2 扩容策略

  1. 双写策略:新旧表同时写入,迁移完成后切换
  2. 全量同步 + 增量同步:先全量迁移,再同步增量 binlog
  3. 灰度切换:逐步将流量从旧分片切换到新分片

【面试官心理】 分库分表是 MySQL 架构中的高级话题。能说清楚分片键选择、跨分片查询、扩缩容方案的候选人,说明他对分布式数据库有深入理解。


级别考察重点期望回答
P5基本概念分库分表的目的、分片策略名称
P6设计能力分片键选择、跨分片查询解决方案
P7架构思维扩缩容方案、分布式 ID、与中间件的配合