共计 5271 个字符,预计需要花费 14 分钟才能阅读完成。
目录
- 何时需要分库分表(动机与门槛)
- 拆分方式:垂直 vs 水平(分片策略)
- 全局唯一 ID:Snowflake / Segment / UUID / 自增偏移
- 分布式事务模型:XA / Saga / TCC / Outbox+CDC
- 跨分片查询与聚合:路由、归并、TopN/ 分页
- 中间件与样例:ShardingSphere(JDBC/Proxy)与读写分离配合
- 约束与一致性:全局唯一、外键、去重与幂等
- 拆分迁移路径:从单库到分片(Cutover 操作手册)
- 校验与对账:一致性校验、分片校验和
- 故障与扩容:热键、失衡、N→M 扩容与重分片
- 监控与容量:关键指标与热点定位
- 实战练习(含参考答案)
版本假设:MySQL 8.0;仍以
shop业务为例。
1) 何时需要分库分表(动机与门槛)
- 单表体量:> 1–3 亿行且写入 / 查询延迟明显恶化;
- 写 QPS/ 锁冲突:热点主键、单分区 / 单索引争用;
- 容量上限:单机磁盘 /IO 吞吐接近瓶颈,Buffer Pool 命中率难上;
- 高可用 / 隔离:按业务域 / 租户隔离,降低故障半径。
优先顺序:索引 /SQL 优化 → 读写分离 / 缓存 → 垂直拆分 → 水平分片。分片带来的复杂度需有明确收益再做。
2) 拆分方式:垂直 vs 水平(分片策略)
2.1 垂直拆分(按业务域分库 / 分表)
- 优点:边界清晰、跨库事务少;
- 风险:跨域查询 / 报表复杂。
2.2 水平分片(同表按键拆)
常见策略:
- Hash:
user_id % N;均衡好,扩容需重分布。 - Range:按时间 /ID 段;便于归档,易冷热不均。
- Directory/Lookup:路由表存
user_id → shard;扩容灵活,依赖路由服务。 - 一致性哈希:虚拟节点平滑扩容,路由复杂度高。
路由键选择:
- 99% 查询要能用路由键定位单分片;
- JOIN 大多在同键域内(如
orders.user_id与users.user_id)。
示例:基于 user_id 的 4 分片命名:
shop_0, shop_1, shop_2, shop_3 -- schema
orders_0..orders_3 -- 物理表
3) 全局唯一 ID:Snowflake / Segment / UUID / 自增偏移
3.1 Snowflake(时间 + 机器 + 序列)
- 位宽示意:
1 | 41-bit 时间 | 10-bit 节点 | 12-bit 序列;有序且高吞吐。 - 注意:时钟回拨需保护(守护进程 / 回拨等待 / 双缓冲)。
3.2 Segment(号段服务)
- 表
id_segments(biz, current, step, version);应用一次取一段,内存发号; - 适合强一致要求不高、吞吐高场景。
3.3 UUID(v4/v7)
- v4 随机、无序不利索引;v7(时间有序)更实用。
3.4 自增偏移(多主键冲突防护)
- 各分片设置:
auto_increment_increment = N,auto_increment_offset = 1..N。 - 仅适用于每个分片本地自增 ; 不解决全局顺序或跨分片并发冲突。
示例:号段表 + 存储过程(简化)
CREATE TABLE IF NOT EXISTS id_segments (biz VARCHAR(32) PRIMARY KEY,
current BIGINT NOT NULL,
step INT NOT NULL DEFAULT 1000,
version BIGINT NOT NULL
);
-- 申请一段(伪原子:在应用侧加重试 / 乐观锁)UPDATE id_segments
SET current = current + step, version = version + 1
WHERE biz='order' AND version = :old_version;
-- 应用内缓存 [old_current+1, new_current]
4) 分布式事务模型:XA / Saga / TCC / Outbox+CDC
4.1 XA 两阶段提交(强一致)
- 流程:
XA START→ 本地操作 →XA END→XA PREPARE→XA COMMIT/ROLLBACK; - 适用:少量跨库强一致;
- 警示:长事务、锁持有、超时 / 故障恢复复杂。
XA 片段(演示)
XA START 'xid-123';
INSERT INTO wallet(user_id, balance) VALUES (1, 100) ON DUPLICATE KEY UPDATE balance=balance+100;
XA END 'xid-123';
XA PREPARE 'xid-123';
-- 跨库另一侧完成 PREPARE 后:XA COMMIT 'xid-123';
4.2 Saga(补偿事务)
- 业务拆为一串本地事务
T1, T2, ...;失败时按逆序执行补偿C2, C1, ...; - 适用:长流程 / 异步可接受,允许最终一致。
4.3 TCC(Try-Confirm-Cancel)
- Try 预留资源,Confirm 生效,Cancel 释放;
- 适合库存、名额等资源预留。
4.4 Outbox + CDC(可靠事件 / 去重)
- 本地事务内写业务表 与 outbox 事件表;CDC 推到消息队列;消费者幂等写。
Outbox 表
CREATE TABLE outbox_events (
id BIGINT PRIMARY KEY,
aggregate_type VARCHAR(32),
aggregate_id VARCHAR(64),
event_type VARCHAR(32),
payload JSON NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed TINYINT(1) NOT NULL DEFAULT 0
) ENGINE=InnoDB;
业务写入(简化)
START TRANSACTION;
INSERT INTO orders(order_id, user_id, amount, status) VALUES(:id,:uid,:amt,'paid');
INSERT INTO outbox_events(id, aggregate_type, aggregate_id, event_type, payload)
VALUES(:id, 'order', :id, 'OrderPaid', JSON_OBJECT('user_id', :uid, 'amount', :amt));
COMMIT; -- CDC 采集 outbox,保证“先写库,后发事件”。
5) 跨分片查询与聚合:路由、归并、TopN/ 分页
5.1 路由
- 以路由键优先:
WHERE user_id=?→ 单分片; - 广播 / 笛卡尔 JOIN 严格限制(配置白名单)。
5.2 归并聚合
- 各分片局部聚合 → 汇聚节点再 SUM/COUNT/MAX 等归并;
COUNT(DISTINCT ...)难以精确合并;- 排序 / 分页:Keyset 分页 优于大 OFFSET。
跨分片 TopN(伪 SQL 流程)
分片并行:SELECT user_id, SUM(amount) gmv FROM orders WHERE ... GROUP BY user_id ORDER BY gmv DESC LIMIT 100
汇聚归并:对各分片 Top100 做 k-way merge,输出全局 Top100
6) 中间件与样例:ShardingSphere(JDBC/Proxy)与读写分离配合
6.1 ShardingSphere-JDBC 最小配置(YAML 片段)
dataSources:
ds_0: {url: jdbc:mysql://db0/shop_0, username: root, password: ***}
ds_1: {url: jdbc:mysql://db1/shop_1, username: root, password: ***}
ds_2: {url: jdbc:mysql://db2/shop_2, username: root, password: ***}
ds_3: {url: jdbc:mysql://db3/shop_3, username: root, password: ***}
rules:
- !SHARDING
tables:
orders:
actualDataNodes: ds_${0..3}.orders_${0..3}
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: mod4
bindingTables:
- orders, order_items
broadcastTables:
- dict_channels
shardingAlgorithms:
mod4:
type: MOD
props:
sharding-count: 4
6.2 读写分离配合(简)
- 写入走主,读取走从;
- 会话 / 事务里需强一致读时使用 强制主库(Hint 或路由规则)。
7) 约束与一致性:全局唯一、外键、去重与幂等
- 全局唯一 :无法依赖单库
UNIQUE;改为 中心化校验 (如 Redis 分布式锁 + 去重键),或 先到先得 的“占位记录”。 - 外键 :跨分片外键不可用;以 应用层约束、写入顺序与离线校验替代。
- 去重 :建立 幂等键 表(
biz_key UNIQUE),重复写入转为更新或忽略。
幂等键示例
CREATE TABLE payments (
id BIGINT PRIMARY KEY,
biz_key VARCHAR(64) UNIQUE,
user_id BIGINT,
amount DECIMAL(10,2),
status ENUM('init','paid','refunded')
);
-- 重复请求直接命中同一 biz_key
8) 拆分迁移路径:从单库到分片(Cutover 操作手册)
- 画像:热点表 /SQL、QPS、数据分布;
- 选键:路由键确定(如
user_id); - 准备:搭建分片集群、全局 ID 服务、读写分离;
- 双写引流:应用同时写老库与新分片(或老→CDC→新);
- 回填:将历史数据迁移到对应分片(批次 / 校验);
- 灰度读:只在新分片读一小部分用户(白名单 / 哈希段);
- 一致性校验:分片与老库按键范围校验和;
- 切换:全量读切新;
- 回收:停止老库写入,保留只读观察一段时间;
- 应急回滚:保留“老库为真”的回切开关与数据追尾方案。
9) 校验与对账:一致性校验、分片校验和
分片校验和(示意)
-- 每分片:按 ID 区间计算摘要
SELECT FLOOR(order_id/100000) AS bucket,
COUNT(*) c, SUM(amount) s, MAX(updated_at) mx
FROM orders
GROUP BY bucket ORDER BY bucket;
将各分片导出后在汇聚层对齐 bucket → (c,s,mx),不一致则回查明细。
10) 故障与扩容:热键、失衡、N→M 扩容与重分片
- 热键 / 热分区:
- 写入热点:把路由键改为
(user_id, salt)或使用随机散列前缀; - 读热点:引入缓存与副本加权读;
- 写入热点:把路由键改为
- 失衡:定期统计各分片容量 /QPS,发现偏斜及时重分布;
- N→M 扩容:
- 目录路由:调整
user → shard映射并迁移对应数据; - 一致性哈希:加入虚拟节点,逐步迁移命中区间;
- 迁移期间双写与“读优先新”策略,完成后回收旧数据。
- 目录路由:调整
11) 监控与容量:关键指标与热点定位
- 库 / 实例:TPS、QPS、活跃会话、锁等待、InnoDB 缓存命中、redo 写入、IO 队列;
- 分片层:各分片 QPS/ 延迟、路由命中率、跨分片查询比例、失败与重试;
- 中间件:连接池利用率、规则命中、黑名单命中、熔断;
- 业务:每路由键写入 / 读取分布(寻找热键)。
12) 实战练习(含参考答案)
练习 1(分片规则):为 orders 设计基于 user_id 的 8 分片规则与表命名,并说明如何保证 orders 与 order_items 在同一分片。
orders_${0..7}, order_items_${0..7}; 规则:mod(user_id, 8)。在应用或中间件里设 bindingTables: orders, order_items,以 user_id 作为分片键,确保同一用户的两表路由一致。
练习 2(全局 ID):对比 Snowflake 与 Segment 的优劣并给出一个号段表结构。
Snowflake:低延迟、有序,依赖时钟;Segment:吞吐高、弱序,依赖中心服务。表:id_segments(biz PK, current BIGINT, step INT, version BIGINT)。
练习 3(Outbox):写出一个 outbox 表并说明为什么它能避免“写库成功但消息丢失”。
在同一本地事务里写业务表与 outbox,事务提交后 CDC 保证事件可靠投递,避免“库与消息双写不一致”。
练习 4(跨分片 TopN):描述如何得到“全局 GMV Top100 用户”。
各分片局部聚合并取 Top100 → 汇聚层做 k-way merge 选全局 Top100。
练习 5(扩容):从 4 分片扩到 8 分片,如何迁移?
目录路由:调整映射并迁移对应键域;一致性哈希:加虚拟节点并逐步搬迁;迁移期双写与读新优先,完成后校验与回收旧分片。
小结
- 分片是“最后的放大器”,先把单库做到极致再拆。
- 选对路由键与全局 ID,是稳定性的根。
- 事务模型按业务选择:XA(强一致、少用)、Saga/TCC(最终一致)、Outbox+CDC(工程落地)。
- 跨分片查询尽量收敛到“单键路由 + 局部聚合 + 汇聚归并”。
- 迁移要有双写、回填、校验、回滚闭环;扩容要可逐步、可观测。
正文完

