MySQL 入门教程 · 第七课

29次阅读
没有评论

共计 5271 个字符,预计需要花费 14 分钟才能阅读完成。

目录

  1. 何时需要分库分表(动机与门槛)
  2. 拆分方式:垂直 vs 水平(分片策略)
  3. 全局唯一 ID:Snowflake / Segment / UUID / 自增偏移
  4. 分布式事务模型:XA / Saga / TCC / Outbox+CDC
  5. 跨分片查询与聚合:路由、归并、TopN/ 分页
  6. 中间件与样例:ShardingSphere(JDBC/Proxy)与读写分离配合
  7. 约束与一致性:全局唯一、外键、去重与幂等
  8. 拆分迁移路径:从单库到分片(Cutover 操作手册)
  9. 校验与对账:一致性校验、分片校验和
  10. 故障与扩容:热键、失衡、N→M 扩容与重分片
  11. 监控与容量:关键指标与热点定位
  12. 实战练习(含参考答案)

版本假设:MySQL 8.0;仍以 shop 业务为例。


1) 何时需要分库分表(动机与门槛)

  • 单表体量:> 1–3 亿行且写入 / 查询延迟明显恶化;
  • 写 QPS/ 锁冲突:热点主键、单分区 / 单索引争用;
  • 容量上限:单机磁盘 /IO 吞吐接近瓶颈,Buffer Pool 命中率难上;
  • 高可用 / 隔离:按业务域 / 租户隔离,降低故障半径。

优先顺序:索引 /SQL 优化 → 读写分离 / 缓存 → 垂直拆分 → 水平分片。分片带来的复杂度需有明确收益再做。


2) 拆分方式:垂直 vs 水平(分片策略)

2.1 垂直拆分(按业务域分库 / 分表)

  • 优点:边界清晰、跨库事务少;
  • 风险:跨域查询 / 报表复杂。

2.2 水平分片(同表按键拆)

常见策略

  • Hashuser_id % N;均衡好,扩容需重分布。
  • Range:按时间 /ID 段;便于归档,易冷热不均。
  • Directory/Lookup:路由表存 user_id → shard;扩容灵活,依赖路由服务。
  • 一致性哈希:虚拟节点平滑扩容,路由复杂度高。

路由键选择

  • 99% 查询要能用路由键定位单分片;
  • JOIN 大多在同键域内(如 orders.user_idusers.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 = Nauto_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 ENDXA PREPAREXA 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 操作手册)

  1. 画像:热点表 /SQL、QPS、数据分布;
  2. 选键:路由键确定(如 user_id);
  3. 准备:搭建分片集群、全局 ID 服务、读写分离;
  4. 双写引流:应用同时写老库与新分片(或老→CDC→新);
  5. 回填:将历史数据迁移到对应分片(批次 / 校验);
  6. 灰度读:只在新分片读一小部分用户(白名单 / 哈希段);
  7. 一致性校验:分片与老库按键范围校验和;
  8. 切换:全量读切新;
  9. 回收:停止老库写入,保留只读观察一段时间;
  10. 应急回滚:保留“老库为真”的回切开关与数据追尾方案。

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 分片规则与表命名,并说明如何保证 ordersorder_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(工程落地)。
  • 跨分片查询尽量收敛到“单键路由 + 局部聚合 + 汇聚归并”。
  • 迁移要有双写、回填、校验、回滚闭环;扩容要可逐步、可观测。

正文完
 0
一诺
版权声明:本站原创文章,由 一诺 于2025-10-07发表,共计5271字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)
验证码