一、分库分表设计基础
(一)分库分表核心目标
- 解决性能瓶颈:当单表数据量超过 1000 万条时,查询性能会显著下降,分库分表可将数据分散存储,提升查询效率。
- 突破存储限制:单库存储容量受服务器磁盘、内存等硬件限制,分库后可利用多服务器的存储资源扩展容量。
- 提高系统可用性:通过分库实现数据冗余存储,避免单库故障导致整个系统瘫痪,提升系统容错能力。
(二)分库分表设计原则
- 均匀性:数据分布应尽量均匀,避免出现部分分表数据量过大(“热点表”)或过小的情况,例如按用户 ID 哈希分表时,需确保哈希算法的分散性。
- 可扩展性:设计时预留扩展空间,当数据量增长时,可通过增加分库分表数量平滑扩容,无需大规模重构。
- 业务适配性:结合业务查询场景选择分片键,例如电商订单表按 “用户 ID” 分片便于查询用户历史订单,按 “创建时间” 分片便于统计某时间段订单数据。
- 最小影响性:分库分表后,对原有业务代码的侵入性应最小化,可通过中间件屏蔽分库分表细节。
二、分库分表常见方案
(一)水平分表(按行拆分)
- 范围分片:按某一字段的范围划分数据,例如订单表按 “创建时间” 分表,order_202301存储 1 月数据,order_202302存储 2 月数据。
- 优点:便于按范围查询(如查询某季度订单),扩容简单。
- 缺点:可能出现热点数据(如当前月份表访问频繁)。
- 哈希分片:对分片键进行哈希计算,将结果映射到不同分表,例如用户表按 “user_id % 10” 分为 10 张表。
- 优点:数据分布均匀,避免热点表。
- 缺点:范围查询需扫描所有分表,实现复杂。
- 列表分片:按字段的枚举值分组,例如按 “地区 ID” 分表,user_beijing存储北京用户,user_shanghai存储上海用户。
- 优点:适配业务逻辑,查询特定列表数据高效。
- 缺点:分片数量固定,不适合枚举值过多的场景。
(二)垂直分表(按列拆分)
- 拆分原则:将大表中不常用的字段或大字段(如 text、blob)拆分到子表,主表保留高频访问字段。例如用户表拆分为user_base(基本信息:ID、姓名、手机号)和user_profile(详细信息:头像、简介)。
- 优势:减少主表数据量,提升查询主表的效率;降低 IO 操作,避免读取无用字段。
(三)分库策略
- 按业务模块分库:将不同业务的表部署到独立数据库,例如电商系统的用户库、订单库、商品库。
- 按分片键分库:在分表基础上进一步分库,例如先按 “user_id % 4” 分 4 个库,每个库再按 “user_id % 8” 分 8 个表,共 32 个分表。
(四)分库分表中间件
- Sharding-JDBC:轻量级中间件,基于 JDBC 层实现,支持分库分表、读写分离,无需部署独立服务,适合 Java 项目。
// Sharding-JDBC配置示例(按user_id哈希分表)shardingsphere: rules: sharding: tables: t_user: actual-data-nodes: ds_${0..1}.t_user_${0..3} database-strategy: standard: sharding-column: user_id sharding-algorithm-name: db_inline table-strategy: standard: sharding-column: user_id sharding-algorithm-name: table_inline sharding-algorithms: db_inline: type: INLINE props: algorithm-expression: ds_${user_id % 2} table_inline: type: INLINE props: algorithm-expression: t_user_${user_id % 4}
- MyCat:基于 Proxy 模式的中间件,支持多种分库分表策略,适合多语言项目,但需单独部署维护。
三、分布式事务处理模式
(一)分布式事务核心挑战
- 一致性:跨多个数据库的事务需保证 “要么全部成功,要么全部失败”,避免出现数据不一致(如订单创建成功但库存未扣减)。
- 性能:分布式事务会增加网络通信和锁开销,可能导致系统性能下降。
- 可用性:部分节点故障时,需确保事务能正常提交或回滚,避免长期阻塞。
(二)常见分布式事务方案
1. 2PC(两阶段提交)
- 流程:
- 准备阶段:协调者向所有参与者发送准备请求,参与者执行事务但不提交,反馈是否就绪。
- 提交阶段:若所有参与者就绪,协调者发送提交请求;否则发送回滚请求。
- 优点:强一致性,适合对一致性要求极高的场景(如金融交易)。
- 缺点:同步阻塞,协调者故障会导致参与者长期锁定资源;可用性差。
2. TCC(Try-Confirm-Cancel)
- 流程:
- Try:检查并预留资源(如扣减库存前锁定商品)。
- Confirm:确认执行业务操作(如实际扣减库存),需保证幂等性。
- Cancel:取消操作并释放资源(如回滚锁定的库存)。
- 示例:电商下单
// Try阶段:锁定库存、冻结余额boolean tryResult = inventoryService.lock(skuId, quantity) && accountService.freeze(userId, amount);if (tryResult) { // 确认阶段:扣减库存、扣减余额 confirmResult = inventoryService.deduct(skuId, quantity) && accountService.deduct(userId, amount); if (!confirmResult) { // 取消阶段:释放库存、解冻余额 inventoryService.unlock(skuId, quantity); accountService.unfreeze(userId, amount); }}
- 优点:性能好,无锁阻塞,适合高并发场景。
- 缺点:需业务侵入式编码,开发成本高。
3. 本地消息表(可靠消息队列)
- 流程:
- 本地事务:在同一个事务中完成业务操作和消息表插入(如订单创建成功后,向消息表插入 “扣减库存” 消息)。
- 消息投递:定时任务扫描消息表,将未投递的消息发送到消息队列。
- 消费消息:接收方消费消息并执行对应操作,完成后更新消息状态。
- 优点:实现简单,低耦合,适合异步场景。
- 缺点:一致性较弱,可能存在短暂数据不一致。
4. SAGA 模式
- 流程:将分布式事务拆分为多个本地事务,每个本地事务对应一个补偿事务,若某步失败,执行前面所有步骤的补偿事务。
- 示例:订单履约流程
- 步骤 1:创建订单(补偿:取消订单)
- 步骤 2:扣减库存(补偿:恢复库存)
- 步骤 3:创建物流单(补偿:取消物流单)
- 优点:支持长事务,可扩展性好。
- 缺点:补偿逻辑复杂,需处理各种异常情况。
(三)分布式事务中间件
- Seata:阿里开源中间件,支持 AT(自动补偿)、TCC、SAGA 等模式,与 Spring Cloud 集成良好,配置简单。
// Seata AT模式示例@GlobalTransactional // 开启全局事务public void createOrder(OrderDTO orderDTO) { orderMapper.insert(orderDTO); // 本地事务1:创建订单 inventoryFeign.deduct(orderDTO.getSkuId(), orderDTO.getQuantity()); // 远程事务:扣减库存 accountFeign.deduct(orderDTO.getUserId(), orderDTO.getAmount()); // 远程事务:扣减余额}
- Hmily:专注于 TCC 模式的中间件,支持注解式编程,简化 TCC 代码开发。
四、实战案例分析
(一)电商订单系统分库分表
- 背景:订单表数据量达 5000 万条,查询响应时间超 3 秒,需进行分库分表优化。
- 设计方案:
- 按 “用户 ID” 哈希分 4 个库,每个库按 “创建时间” 分 12 个表(每月一张表),共 48 个分表。
- 分片键选择:主分片键为 “user_id”(便于查询用户订单),辅助分片键为 “create_time”(便于统计分析)。
- 中间件:使用 Sharding-JDBC,通过读写分离将查询请求路由到从库。
- 优化效果:单表数据量降至约 100 万条,查询响应时间缩短至 200ms 以内。
(二)支付系统分布式事务
- 背景:用户支付时需同时更新订单状态(订单库)和扣减余额(账户库),需保证事务一致性。
- 解决方案:
- 采用 Seata AT 模式,订单服务作为协调者,账户服务作为参与者。
- 订单服务执行update order set status='PAID',账户服务执行update account set balance=balance-amount。
- 异常处理:若账户服务扣减失败,Seata 自动回滚订单状态更新。
- 效果:事务成功率达 99.99%,无数据不一致情况,性能损耗控制在 10% 以内。
五、分库分表与分布式事务最佳实践
(一)分库分表注意事项
- 避免跨库事务:设计时尽量将相关表放在同一库中,减少分布式事务场景。
- 索引设计:分表后需在分片键上建立索引,非分片键查询需通过中间件路由或全局索引(如 Elasticsearch)优化。
- 数据迁移:使用工具(如 ShardingSphere Migration)平滑迁移历史数据,迁移过程中需暂停写入或使用双写策略。
(二)分布式事务选择策略
- 强一致性场景(如金融支付):优先选择 2PC 或 TCC。
- 高并发场景(如电商下单):选择 SAGA 或本地消息表。
- 低延迟要求场景:避免分布式事务,通过业务设计(如最终一致性)降低复杂度。
通过合理的分库分表设计和适配的分布式事务方案,可有效解决大规模数据存储和跨库事务一致性问题,为高可用、高并发系统提供坚实的底层支撑。