
MySQL 实战精通系列 · 第10篇分库分表与分布式事务实战本篇目标单机扛不住、数据量太大、跨库怎么保证一致——从分片策略到分布式事务系统化掌握 MySQL 水平扩展方案。文章目录MySQL 实战精通系列 · 第10篇分库分表与分布式事务实战一、什么时候需要分库分表1.1 单机瓶颈的三个信号1.2 一张图看懂决策顺序二、分片策略垂直 vs 水平2.1 两种拆分方式2.2 垂直拆分按业务分库2.3 水平拆分按数据行分表三、分片算法3.1 常用分片算法对比3.2 Range 分片3.3 Hash 分片3.4 雪花算法全局唯一 ID四、分库分表中间件4.1 两种技术路线4.2 ShardingSphere-JDBC 配置示例4.3 分片后的 SQL 限制五、分库分表实战订单表拆分5.1 需求分析5.2 分片键选择5.3 建表 SQL5.4 路由计算5.5 跨分片分页查询六、分布式事务6.1 为什么需要分布式事务6.2 分布式事务方案对比6.3 Seata AT 模式实战6.4 本地消息表最终一致方案七、扩容方案7.1 为什么要考虑扩容7.2 扩容方案对比7.3 双写迁移实战八、实战任务任务清单自检问题九、本篇小结一、什么时候需要分库分表1.1 单机瓶颈的三个信号单机 MySQL 扛不住的信号 │ ├── ① 数据量太大 ← 单表 2000万行B树层高增加查询变慢 ├── ② 写入压力太大 ← 单机 TPS 到瓶颈主库 CPU 打满 ├── ③ 磁盘空间不够 ← 单机磁盘容量到上限原则能不拆就不拆。分库分表是最后手段优先考虑读写分离、索引优化、归档冷数据。1.2 一张图看懂决策顺序数据库扛不住了 │ ↓ ① SQL 和索引优化了吗 │ 没有 → 先优化 SQL 和索引 │ ↓ 优化了 ② 读写分离做了吗 │ 没有 → 先做读写分离读压力分摊 │ ↓ 做了 ③ 冷数据归档了吗 │ 没有 → 先归档历史数据减少单表体积 │ ↓ 归档了 ④ 还是扛不住 │ 是 → 分库分表二、分片策略垂直 vs 水平2.1 两种拆分方式维度垂直拆分水平拆分拆分对象按业务/字段拆按数据行拆典型场景用户库、订单库、商品库分离订单表拆成 orders_0~orders_9优点业务解耦、专库专用单表数据量可控缺点跨库 JOIN 困难分片键选择困难复杂度低高2.2 垂直拆分按业务分库拆分前 shop 库所有表在一起 拆分后 shop_user 库 → user, address shop_order 库 → orders, order_items, payment shop_product 库 → product, category垂直拆分原则① 按业务关联度拆强关联的表放一起 ② 按访问频率拆高频表和低频表分离 ③ 按数据量拆大表独立 ④ 避免跨库 JOIN能冗余就冗余2.3 水平拆分按数据行分表拆分前 orders 表5000万行 拆分后 orders_0 表500万行 orders_1 表500万行 ... orders_9 表500万行分片键选择分片键优点缺点user_id同一用户订单集中商家维度查询跨片order_id分布均匀用户维度查询跨片create_time便于归档热点数据集中基因法兼顾多维度实现复杂基因法示例订单 ID 中嵌入 user_id 的后几位使 order_id 和 user_id 都能定位到同一分片。三、分片算法3.1 常用分片算法对比算法原理优点缺点Range按范围分id 1-100万 → 分片0扩容方便热点集中Hashhash(key) % N分布均匀扩容需 rehash一致性 Hash环上取模扩容影响小实现复杂雪花算法生成全局唯一 ID趋势递增依赖时钟3.2 Range 分片-- 按 order_id 范围分片orders_0: order_id1~10000000orders_1: order_id10000001~20000000orders_2: order_id20000001~30000000适用订单、日志等有时间顺序的数据。问题新数据集中写入最后一个分片造成热点。3.3 Hash 分片-- 按 user_id 取模分片分片号user_id%10适用用户维度查询为主的场景。问题扩容时需要数据迁移10 片扩到 20 片几乎所有数据都要动。3.4 雪花算法全局唯一 IDSnowflake ID 结构64 位 │ ├── 1 位符号位固定 0 ├── 41 位时间戳毫秒级可用 69 年 ├── 10 位机器 ID1024 台机器 └── 12 位序列号每毫秒 4096 个 ID 特点 ① 全局唯一 ② 趋势递增利于 B 树索引 ③ 本地生成无网络开销 ④ 每秒可生成 400 万 ID为什么不用自增 ID单库自增分库后 ID 会冲突 UUID无序作为主键会导致 B 树频繁分裂 雪花算法有序 唯一 高性能四、分库分表中间件4.1 两种技术路线路线代表原理优点缺点客户端分片ShardingSphere-JDBC应用层计算路由性能好、无额外部署应用侵入、多语言支持差代理分片ShardingSphere-Proxy、MyCat独立代理层应用透明、多语言多一跳、代理需高可用4.2 ShardingSphere-JDBC 配置示例spring:shardingsphere:datasource:names:ds0,ds1ds0:type:com.zaxxer.hikari.HikariDataSourcejdbc-url:jdbc:mysql://127.0.0.1:3306/shop_0username:rootpassword:root123ds1:type:com.zaxxer.hikari.HikariDataSourcejdbc-url:jdbc:mysql://127.0.0.1:3306/shop_1username:rootpassword:root123rules:sharding:tables:orders:actual-data-nodes:ds$-{0..1}.orders_$-{0..1}table-strategy:standard:sharding-column:user_idsharding-algorithm-name:orders-inlinekey-generate-strategy:column:order_idkey-generator-name:snowflakesharding-algorithms:orders-inline:type:INLINEprops:algorithm-expression:orders_$-{user_id % 2}4.3 分片后的 SQL 限制分库分表后不能用的 SQL ✗ 跨库 JOIN ✗ 跨库事务 ✗ 不包含分片键的查询会广播到所有分片 ✗ 跨库 ORDER BY LIMIT ✗ 跨库 COUNT(*)、GROUP BY ✗ 跨库 UPDATE/DELETE解决方案问题方案跨库 JOIN冗余字段、宽表、ES 检索跨库分页各分片查 N 条内存归并跨库 COUNT各分片求和、维护计数表跨库事务分布式事务见第六节五、分库分表实战订单表拆分5.1 需求分析订单表 orders ├── 当前数据量5000 万行 ├── 日增50 万行 ├── 查询模式 │ ├── 用户查自己的订单user_id 维度80% │ ├── 商家查店铺订单shop_id 维度15% │ └── 运营查全量订单跨维度5% └── 目标拆成 4 库 × 4 表 16 片5.2 分片键选择主分片键user_id覆盖 80% 查询 基因法将 shop_id 后 4 位嵌入 order_id → 商家查询时通过 shop_id 反推分片5.3 建表 SQL-- 在每个分片库中执行CREATETABLEorders_0(order_idBIGINTNOTNULL,user_idBIGINTNOTNULL,shop_idBIGINTNOTNULL,total_amountDECIMAL(10,2)NOTNULL,statusTINYINTNOTNULL,created_atDATETIMENOTNULLDEFAULTCURRENT_TIMESTAMP,PRIMARYKEY(order_id),KEYidx_user_created(user_id,created_at),KEYidx_shop_created(shop_id,created_at))ENGINEInnoDB;-- orders_1 ~ orders_15 结构相同5.4 路由计算// 用户维度查询intdbIndexuserId%4;inttableIndexuserId%4;// 商家维度查询基因法longgeneshopId0xF;// 取后4位intshopDbIndex(int)(gene%4);intshopTableIndex(int)(gene%4);5.5 跨分片分页查询-- 需求查询第 100 页每页 20 条-- 错误做法每个分片查 LIMIT 2000, 20 → 结果不对-- 正确做法每个分片查 LIMIT 0, 2020内存归并后取 2000-2020-- 第1步各分片查询SELECT*FROMorders_0ORDERBYcreated_atDESCLIMIT2020;SELECT*FROMorders_1ORDERBYcreated_atDESCLIMIT2020;-- ... 所有分片-- 第2步内存归并排序取第 2000-2020 条优化使用游标分页记录上一页最后一条的 created_at order_id避免深度分页。六、分布式事务6.1 为什么需要分布式事务场景用户下单 ① 订单库创建订单 ② 库存库扣减库存 ③ 账户库扣减余额 问题步骤 ② 失败步骤 ① 已提交数据不一致6.2 分布式事务方案对比方案一致性性能复杂度适用场景2PC/XA强一致差中传统金融TCC强一致中高支付、交易本地消息表最终一致好中订单、通知事务消息最终一致好中异步解耦Seata AT强一致中低通用场景Saga最终一致好中长流程6.3 Seata AT 模式实战核心角色Seata 三大角色 │ ├── TC (Transaction Coordinator) ← 事务协调器独立部署 ├── TM (Transaction Manager) ← 事务管理器发起方 └── RM (Resource Manager) ← 资源管理器各数据库AT 模式原理① 业务 SQL 执行前记录 before image ② 执行业务 SQL ③ 执行后记录 after image ④ 注册分支事务到 TC ⑤ 全局提交 → 删除 undo log ⑥ 全局回滚 → 用 undo log 反向补偿代码示例GlobalTransactionalpublicvoidcreateOrder(OrderDTOorder){// ① 订单库创建订单orderMapper.insert(order);// ② 库存库扣减库存inventoryService.deduct(order.getProductId(),order.getQuantity());// ③ 账户库扣减余额accountService.deduct(order.getUserId(),order.getTotalAmount());}6.4 本地消息表最终一致方案本地消息表方案 │ ├── ① 业务库订单创建 消息写入同一本地事务 ├── ② 定时任务扫描未发送消息 ├── ③ 发送消息到 MQ ├── ④ 下游消费消息执行库存扣减 └── ⑤ 消费成功更新消息状态建表 SQLCREATETABLElocal_message(idBIGINTPRIMARYKEYAUTO_INCREMENT,biz_typeVARCHAR(50)NOTNULL,biz_idBIGINTNOTNULL,payload JSONNOTNULL,statusTINYINTNOTNULLDEFAULT0,-- 0待发送 1已发送 2已消费retry_countINTNOTNULLDEFAULT0,created_atDATETIMEDEFAULTCURRENT_TIMESTAMP,KEYidx_status_created(status,created_at));七、扩容方案7.1 为什么要考虑扩容初始4 库 × 4 表 16 片 增长数据量翻倍需要扩到 8 库 × 8 表 64 片 问题hash(user_id) % 4 变成 % 8几乎所有数据都要迁移7.2 扩容方案对比方案原理优点缺点停机扩容停服迁移简单业务中断双写迁移新旧库同时写平滑实现复杂一致性 Hash环上迁移影响小实现复杂倍增扩容4→8只迁一半迁移量小需规划7.3 双写迁移实战双写迁移步骤 │ ├── ① 新库建好结构一致 ├── ② 应用双写同时写旧库和新库 ├── ③ 数据同步将旧库存量数据迁移到新库 ├── ④ 数据校验对比新旧库数据一致性 ├── ⑤ 读切换读流量逐步切到新库 ├── ⑥ 停写旧库确认无误后停止双写 └── ⑦ 下线旧库八、实战任务任务清单设计订单表的分片方案分片键 算法搭建 ShardingSphere-JDBC 环境实现订单表的水平分片实现跨分片分页查询用 Seata 实现下单分布式事务用本地消息表实现最终一致模拟扩容4 库扩到 8 库自检问题什么时候需要分库分表决策顺序是什么垂直拆分和水平拆分的区别常用分片算法有哪些各自的优缺点雪花算法的结构是什么为什么不用自增 ID分库分表后哪些 SQL 不能用怎么解决分布式事务有哪些方案各自适用什么场景Seata AT 模式的原理是什么扩容有哪些方案双写迁移的步骤九、本篇小结第10篇 核心收获 │ ├── 分片决策 │ ├── 先优化 SQL 和索引 │ ├── 再读写分离 │ ├── 再冷数据归档 │ └── 最后才分库分表 │ ├── 分片方式 │ ├── 垂直拆分按业务分库 │ └── 水平拆分按数据行分表 │ ├── 分片算法 │ ├── Range范围分片扩容方便热点集中 │ ├── Hash分布均匀扩容需 rehash │ └── 雪花算法全局唯一 ID趋势递增 │ ├── 中间件 │ ├── ShardingSphere-JDBC客户端分片 │ └── ShardingSphere-Proxy代理分片 │ ├── 分片后限制 │ ├── 跨库 JOIN → 冗余字段/宽表 │ ├── 跨库分页 → 内存归并 │ ├── 跨库 COUNT → 分片求和 │ └── 跨库事务 → 分布式事务 │ ├── 分布式事务 │ ├── 2PC/XA强一致性能差 │ ├── TCC强一致复杂度高 │ ├── Seata AT强一致通用 │ ├── 本地消息表最终一致 │ └── 事务消息最终一致 │ └── 扩容方案 ├── 双写迁移平滑扩容 ├── 一致性 Hash影响小 └── 倍增扩容迁移量小下一篇第11篇《Redis 缓存与 MySQL 一致性实战》