一句话总结
分库分表是解决单表数据量过大的方案。分为垂直拆分(按业务拆分库/按列拆分表)和水平拆分(按行拆分到多个库/表)。分片策略有范围分片(简单但热点不均)、哈希分片(均匀但扩容难)、一致性哈希(均匀+易扩容)。常用中间件:ShardingSphere-JDBC(客户端)和 MyCat(代理)。核心挑战:分布式事务、跨库 JOIN、全局 ID。
初级理解
垂直拆分 vs 水平拆分
| 拆分方式 | 操作 | 示例 | 解决的问题 |
|---|---|---|---|
| 垂直分库 | 按业务拆库 | 用户库、订单库、商品库 | 业务解耦,独立扩容 |
| 垂直分表 | 按列拆表 | 用户主表(id,name)+ 用户详情表(id,address,bio) | 减少单表宽度,热门列和冷门列分离 |
| 水平分库 | 按行拆到多个库 | user_db_0, user_db_1 | 突破单库连接数和容量限制 |
| 水平分表 | 按行拆到多个表 | order_0, order_1, order_2 | 突破单表数据量瓶颈 |
# 什么时候需要分库分表?
# 1. 单表数据量超过 500 万 ~ 2000 万(取决于行大小和查询复杂度)
# 2. 单库连接数不够用
# 3. 磁盘 IO 成为瓶颈
# 优先考虑优化而非分库分表:
# 1. 索引优化 + SQL 优化
# 2. 读写分离
# 3. 缓存(Redis)
# 4. 历史数据归档
一句话总结:垂直拆业务/列,水平拆行;优先优化,实在不行再分库分表。
中级深入
分片策略对比
| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 范围分片 | id 1~1000 在表0,1001~2000 在表1 | 简单,易扩容 | 热点不均(新数据集中) |
| 哈希分片 | id % 4 决定表 | 数据均匀 | 扩容需全量迁移 |
| 一致性哈希 | 哈希环 + 虚拟节点 | 均匀 + 扩容只迁移部分 | 实现复杂 |
| 地理位置分片 | 按地区分库 | 就近访问 | 跨地区查询困难 |
ShardingSphere-JDBC 实战
# application.yml
spring:
shardingsphere:
datasource:
names: ds0, ds1
ds0:
url: jdbc:mysql://localhost:3306/db0
ds1:
url: jdbc:mysql://localhost:3306/db1
rules:
sharding:
tables:
t_order:
# 分库策略:user_id % 2
actual-data-nodes: ds$->{0..1}.t_order_$->{0..1}
database-strategy:
standard:
sharding-column: user_id
sharding-algorithm-name: db-inline
# 分表策略:order_id % 2
table-strategy:
standard:
sharding-column: order_id
sharding-algorithm-name: tbl-inline
sharding-algorithms:
db-inline:
type: INLINE
props:
algorithm-expression: ds$->{user_id % 2}
tbl-inline:
type: INLINE
props:
algorithm-expression: t_order_$->{order_id % 2}
全局 ID 生成方案
# 方案1:雪花算法(Snowflake)
# 64位:1位符号 + 41位时间戳 + 10位机器ID + 12位序列号
# 优点:高性能、趋势递增、不依赖数据库
# 缺点:依赖时钟,时钟回拨可能重复
# 方案2:号段模式(美团 Leaf)
# 从数据库批量获取 ID 号段,缓存在内存中
# 优点:高性能、趋势递增
# 缺点:依赖数据库
# 方案3:Redis 自增
INCR order_id # 原子自增
# 优点:简单
# 缺点:依赖 Redis,持久化可能丢数据
中级要点:哈希分片数据均匀但扩容难;ShardingSphere-JDBC 无代理性能好;雪花算法是常用全局 ID 方案。
高级拓展
分库分表后的挑战
| 挑战 | 解决方案 |
|---|---|
| 分布式事务 | Seata AT/TCC、可靠消息最终一致性 |
| 跨库 JOIN | 字段冗余、全局表(广播表)、应用层组装 |
| 跨库分页/排序 | 每个库查 TopN → 应用层归并排序 |
| 全局唯一 ID | 雪花算法、号段模式 |
| 数据迁移 | 双写 + 历史数据迁移 + 灰度切换 |
数据迁移方案
# 平滑迁移步骤(不停机)
# 1. 双写:新数据同时写旧库和新分片
# 2. 历史数据迁移:用定时任务分批迁移旧数据
# 3. 数据校验:对比新旧数据是否一致
# 4. 灰度切换:先切部分流量到新分片
# 5. 全量切换:确认无误后全量切到新分片
# 6. 下线旧库
# 工具推荐
# - ShardingSphere-Scaling:官方数据迁移工具
# - Canal + 自定义同步:基于 binlog 的增量同步
实战场景
场景:订单表水平分表(按用户 ID 哈希)
# 分 4 个库,每个库 4 张表,共 16 个分片
# 路由规则:user_id % 4 → 库,order_id % 4 → 表
# 查询:根据分片键查询(高效)
SELECT * FROM t_order WHERE user_id = 100 AND order_id = 200;
# → 路由到 ds0.t_order_0
# 查询:不带分片键(全分片扫描,低效)
SELECT * FROM t_order WHERE status = 'paid';
# → 需要查所有 16 个分片,然后归并结果
# 最佳实践:
# 1. 所有查询尽量带分片键
# 2. 非分片键查询走 ES 等搜索引擎
# 3. 广播表(字典表)每个库都存一份
面试模拟
面试官:什么时候需要分库分表?
你:单表数据量超过千万级、单库连接数不够、磁盘 IO 瓶颈时考虑。但优先做索引优化、SQL 优化、读写分离、缓存,实在不行再分库分表,因为分库分表会引入分布式事务、跨库 JOIN 等复杂性。
面试官:哈希分片扩容怎么办?
你:普通哈希(id % 4)扩容需要全量迁移。解决方案:1)一致性哈希——只迁移部分数据;2)预分片——一开始就分 32 个逻辑片,物理上先合并部署,扩容时迁移逻辑片;3)双写迁移——新数据双写,历史数据分批迁移,灰度切换。