分库分表实施

项目仓库:java-concurrency-security:用于学习使用的的项目。基于 Spring Boot 2.7 的高并发性能调优 + 安全监控完整脚手架,涵盖 JVM 调优、线程池优化、Redis 缓存防护、分库分表、OAuth2.0 认证、API 网关、ELK 日志收集、SkyWalking APM 监控等核心能力。 - AtomGit | GitCode


前言:为什么要分库分表?

类比: 把数据库想象成图书馆。

  • 问题 1: 图书馆只有一个管理员,借书还书的队排到街上了 → 单库连接数不够
  • 问题 2: 书架太多,读者找书要花半小时 → 单表数据太多,查询慢
  • 问题 3: 消防通道只能过 1 个人,遇到紧急情况太危险 → 备份慢,恢复慢

解决方案: 开分馆(分库),每个馆多放几个书架(分表),让客流分散。


1. 什么时候该分?

不要为了"将来"分,要为"现在"分。 过早分库分表会增加复杂度,不分又扛不住业务。

指标 阈值 说明 通俗判断
单表行数 > 500 万 查询性能开始下降 单本"读者登记簿"已经记不下了
单库 QPS > 3000 连接池和 IO 瓶颈 管理员忙不过来
单库大小 > 100GB 备份恢复时间过长 整馆搬一次要花 2 小时

经验法则: 假如你的订单表已经有 1 亿行,单表查询要 10 秒 → 该分了。 如果只有 10 万行,但偶尔查询慢 → 先加索引,别急分。


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

2.1 什么是水平分片?

类比: 水平分片 = 同一个商品分到多个货架上,每个货架摆一部分。 京东商城有 10 亿件商品,分布在 100 个仓库,每个仓库负责 1000 万件。

2.2 分片架构

本项目采用 2 库 × 4 表 = 8 张表 的水平分片方案:

shardingSphere 是什么? 它像一个"智能路由器", 应用说"我要查 user_id=123 的订单",它自动算出来"这条数据在 ds1 的 t_order_2 表里", 然后直接去那里取。应用层完全无感知。

2.3 分片键选择(最关键的一步)

分片键选错了,会导致全表扫描,分库分表白做!

标准 说明 反面例子
高频查询 选择 WHERE 条件中最常用的字段 status(订单状态),但实际很少按状态查
数据均匀 避免数据倾斜 is_vip(会员标记),90% 用户都是非会员
避免跨片 关联查询尽量在同一分片 订单用 user_id 分,商品用 product_id 分,关联查询要扫所有库
业务相关 选择业务维度 create_time(时间),会导致数据集中在某段时间内

本项目选择:

  • 分库键:user_id(同一个用户的所有订单一定在同一个库,查询订单不用跨库)
  • 分表键:order_id(分散写入压力,避免单表热点)

为什么用 user_id 分库而不是 order_id?

  • 用户的订单查询占 80% 的请求 → 按 user_id 分可以 100% 命中单一库
  • 按 order_id 分库的话,查"我所有订单"要扫所有库

2.4 分片算法

复制代码
# 分库:user_id 对 2 取模(结果 0→ds0,1→ds1)
# 举例:user_id=123,123%2=1,所以订单存到 ds1

# 分表:order_id 对 4 取模(结果 0~3 对应 4 张表)
# 举例:order_id=10086,10086%4=2,所以存到 t_order_2

为什么用 HASH_MOD 而不用 MOD? 哈希取模对连续值(如 10086、10087)分布更均匀, 而普通取模会让相邻的 order_id 落在同一张表,造成热点。

2.5 分布式 ID(雪花算法)

问题: 数据分散到 8 张表后,ID 不能用数据库自增了! 因为两个库同时自增,ID 会重复。
类比: 分布式 ID 就像给每个商品印"全球唯一编码",格式是: 地区码 + 日期 + 流水号,地球上任何地方都不会重复。

|---------|-----------|------------|-----------|
| 1bit 符号 | 41bit 时间戳 | 10bit 工作机器 | 12bit 序列号 |
| 0 | 毫秒级时间 | 机器ID+线程 | 每毫秒序号 |
| 不使用 | 精确到毫秒 | 区分机器 | 每毫秒自增 |

优势:

  • ✅ 全局唯一(包含时间戳+机器ID+序列号)
  • ✅ 趋势递增(按时间排序,范围查询快)
  • ✅ 高性能(本地生成,不用等数据库)

3. 垂直分片(按列拆分)

3.1 垂直分库(按业务域)

类比: 大公司拆分成不同部门,每个部门管自己那摊事。 用户部只管用户数据,订单部只管订单数据,互不干扰。

什么时候用垂直分库? 不同业务的"用户群体"差异很大时。 比如商品库可能要 1 万 QPS,但用户库只有 100 QPS,混在一起就要按最高的配。

3.2 垂直分表(大字段拆分)

类比: 餐厅的菜单,主菜单印常点的菜(爆款),附加单印大菜(海鲜)。 主菜单薄薄的翻得快,海鲜的菜单独放在最后几页。

复制代码
主表 t_product(高频查询,每次 SELECT 都用到)
├── id, name, price, stock, status     -- 字段小,常查询

扩展表 t_product_detail(低频大字段)
├── product_id                          -- 关联主表
├── description TEXT                    -- 商品详情,几千字
├── images JSON                         -- 图片列表
├── specs JSON                          -- 规格参数

什么时候用? 某条记录的某些字段特别大(TEXT、BLOB、JSON), 每次查询都把它们读出来很浪费(慢、占 IO、占内存)。


4. 实施步骤

4.1 数据迁移流程

一句话:双写→校验→切读→切写→观察→下线

千万不要一次切换! 大厂踩过的坑: 某公司"双 11"前想当然地一刀切,结果新表分片键配置错, 导致全公司订单数据错乱,最后紧急回滚,影响交易 4 小时。

4.2 跨分片查询处理

场景 解决方案 类比
按 user_id 查订单 直接路由到对应分片 在指定分馆找书
按 order_id 查订单 先查映射表,再路由 不知道分馆就先查"图书总目录"
全局搜索订单 用 ES 异构索引 找专业图书检索员(ES)
跨分片分页 禁止!改用 ES 或游标 不允许"把 8 个馆的书全搬到一起数"

4.3 分布式事务

问题: 订单表在订单库,库存表在商品库,账户表在用户库 → 怎么保证要么都成功,要么都失败?

java 复制代码
@GlobalTransactional   // Seata AT 模式,自动管理分布式事务
public void createOrder(OrderDTO dto) {
    orderMapper.insert(order);       // 订单库:插入订单
    inventoryService.deduct();       // 商品库:扣库存
    accountService.debit();          // 用户库:扣余额
}
// 三步要么都成功,要么都回滚

Seata 是什么? 它像一个"协调员",每个分库都向协调员汇报, 协调员说"都成功"才提交,说"有失败"就全部回滚。


5. ShardingSphere 配置详解

参见 application.yml 中的 spring.shardingsphere 配置节。

5.1 关键配置项

配置项 作用 类比
actual-data-nodes 实际的数据节点列表 "我们有哪些仓库"
database-strategy 分库策略 "按什么分到不同仓库"
table-strategy 分表策略 "仓库内怎么分货架"
key-generator 分布式主键 "商品编码规则"
sharding-algorithms 分片算法 "具体怎么分"

5.2 常见问题

Q:分库后还能 JOIN 吗? A:尽量避免。JOIN 要求扫描多个库,性能极差。改为:

  • 数据冗余(在订单表冗余商品名称)
  • 内存组装(先查订单列表,再批量查商品 Map)
  • ES 异构索引(最灵活)

Q:分表后怎么分页? A:禁止 LIMIT 100000, 20(要扫 100020 条记录)。 改用:

  • 游标分页(WHERE id > 100020 LIMIT 20
  • ES 全文检索后分页
  • 前端"加载更多"模式
相关推荐
多巴胺梦想家4 小时前
事务与并发控制:当多人同时操作数据库
服务器·数据库·oracle
howard20055 小时前
PostgreSQL起步
数据库·postgresql
秋田君5 小时前
QT_QT布局详解
开发语言·数据库·qt
可乐ea6 小时前
【Redis八股|第8篇】Redis 分布式锁原理与 Redisson 使用
数据库·redis·分布式·面试题·redis八股
ClouGence7 小时前
Oracle 到 OceanBase 迁移方案横评:停机导出/导入 vs OMS vs CDC 工具
数据库·oracle
Meya11277 小时前
不同规模机房怎么选 U 位系统?8 柜小型机房、40 柜数据中心完整选型参考
服务器·网络·数据库
从此以后自律8 小时前
MySQL 删除数据全方式详解
数据库·mysql
青山木9 小时前
Redis 高可用的最后一公里:Cluster 分片、Gossip 与故障转移全流程
数据库·redis·后端·缓存
蓝胖的四次元口袋9 小时前
JavaString知识梳理
数据库·oracle
csdn_aspnet10 小时前
mysql 使用逗号拼接一列数据
数据库·mysql·group_concat