shardinglisted
Install: claude install-skill 1398281322-a11y/java-backend-guardrails
# 分库分表
## When to Invoke
单机归档/分区/索引之后,容量或写入仍明显不够;或已有分片要改。用户点名分库分表时也读本文件,但默认结论可以是「现在不上」。
## When NOT
慢 SQL → `mysql-index`。表结构/归档/分区 → `table-design`。面试背「分库分表」不等于业务现在要做。
## 风险(面试考点)
拆了之后:跨库 join、分布式事务、扩容迁数、全局唯一 ID、分页排序都变难。选错分片键(按状态、按日期当唯一键)会导致热点或查一次扫全分片。
分区表 ≠ 分库分表。分区仍是一个 MySQL 实例。
## 适用场景
- 订单按 `user_id` 查询占 90%,单库写入顶不住
- 流水按 `merchant_id` 查询,单表归档后仍过大
## 方案选型(轻量优先)
| 优先级 | 做法 | 条件 |
|--------|------|------|
| 0 | 不上分片 | 默认 |
| 1 | 归档 + 分区 + 正确索引 | 先做完再评估 |
| 2 | 单库分表(同实例) | 单表过大但实例 IO 还行 |
| 3 | 分库分表 | 实例容量/连接/写入都不行 |
升级触发(要能说出数字):单表 > 千万且归档无效,或写入打满单实例磁盘/CPU,且查询有稳定等值键。
## 默认方案(真要拆时)
分片键 = **最常用等值查询的那个列**,且写入分散:C 端订单用 `user_id`,B 端商户后台若也是主入口,考虑订单表冗余或映射表,不要幻想一个键同时完美服务两端。
```text
shard = user_id % 16 # 简单取模,扩容要迁移
# 或一致性哈希:扩容迁移更少,运维更复杂
```
规则:
- 所有查询尽量带分片键
- 禁止跨分片 join;应用侧聚合或宽表
- ID 用雪花(`distributed-id`),按分片键路由不靠自增
- 跨分片分页:禁止简单 `LIMIT` 合成;用 ES/宽表/业务限制必须带 user_id
- 跨分片写:不要上 Seata 当默认,见 `distributed-transaction`
订单号可内嵌分片信息,便于客服按单号路由。
## 反例
错误:一上来 8 库 64 表,列表还要「全平台按时间排序」。
正确:这种查询形态不适合分片;先改查询或上检索。
错误:用 `status` 当分片键。
正确:状态基数低,热点严重,且状态会变(迁移噩梦)。
错误:把「加个联合索引」写进分片方案。
正确:回 `mysql-index`。
## 验证
- 主路径 SQL 都带分片键,能路由到单片。
- 扩容/迁移有演练,不是只写了算法。
- 不存在隐藏全分片扫描的管理接口打生产。
## 评审清单
- [ ] 是否其实只用索引/归档就够
- [ ] 分片键是等值高频列且不可变
- [ ] 跨片 join/排序有交代
- [ ] 没有和索引 skill 混写
---