10亿订单如何做分库分表?

MySQL关系型数据库数据库管理服务

大家好,我是苏三,又跟大家见面了。

前言

场景痛点 :某电商平台的MySQL订单表达到7亿行时,出现致命问题:

  
-- 简单查询竟需12秒!  
SELECT * FROM orders WHERE user\_id=10086 LIMIT 10;  
  
-- 统计全表耗时278秒  
SELECT COUNT(*) FROM orders;  

核心矛盾

  1. B+树索引深度达到5层,磁盘IO暴增。
  2. 单表超200GB导致备份时间窗突破6小时。
  3. 写并发量达8000QPS,主从延迟高达15分钟。

关键认知 :当单表数据量突破5000万行 时,就该启动分库分表设计预案。

那么问题来了,假如现在有10亿的订单数据,我们该如何做分库分表呢?

今天这篇文章就跟大家一起聊聊这个问题,希望对你会有所帮助。

1 分库分表核心策略

1.1 垂直拆分:先给数据做减法

picture.image

优化效果

  • 核心表体积减少60%
  • 高频查询字段集中提升缓存命中率

1.2 水平拆分:终极解决方案

分片键选择三原则

  1. 离散性 :避免数据热点(如user_id优于status)
  2. 业务相关性 :80%查询需携带该字段
  3. 稳定性 :值不随业务变更(避免使用手机号)

分片策略对比

| 策略类型 | 适用场景 | 扩容复杂度 | 示例 | | --- | --- | --- | --- | | 范围分片 | 带时间范围的查询 | 简单 | create_time按月分表 | | 哈希取模 | 均匀分布 | 困难 | user_id % 128 | | 一致性哈希 | 动态扩容 | 中等 | 使用Ketama算法 | | 基因分片 | 避免跨分片查询 | 复杂 | 从user_id提取分库基因 |

2 基因分片

针对订单系统的三大高频查询:

  1. 用户查历史订单(user_id)
  2. 商家查订单(merchant_id)
  3. 客服按订单号查询(order_no)

解决方案

picture.image

Snowflake订单ID改造

  
// 基因分片ID生成器  
publicclass OrderIdGenerator {  
    // 64位ID结构:符号位(1)+时间戳(41)+分片基因(12)+序列号(10)  
    privatestaticfinalint GENE\_BITS = 12;  
      
    public static long generateId(long userId) {  
        long timestamp = System.currentTimeMillis() - 1288834974657L;  
        // 提取用户ID后12位作为基因  
        long gene = userId & ((1 << GENE\_BITS) - 1);   
        long sequence = ... // 获取序列号  
          
        return (timestamp << 22)   
             | (gene << 10)   
             | sequence;  
    }  
      
    // 从订单ID反推分片位置  
    public static int getShardKey(long orderId) {  
        return (int) ((orderId >> 10) & 0xFFF); // 提取中间12位  
    }  
}  

路由逻辑

  
// 分库分表路由引擎  
publicclass OrderShardingRouter {  
    // 分8个库 每个库16张表  
    privatestaticfinalint DB\_COUNT = 8;   
    privatestaticfinalint TABLE\_COUNT\_PER\_DB = 16;  
      
    public static String route(long orderId) {  
        int gene = OrderIdGenerator.getShardKey(orderId);  
        int dbIndex = gene % DB\_COUNT;  
        int tableIndex = gene % TABLE\_COUNT\_PER\_DB;  
          
        return"order\_db\_" + dbIndex + ".orders\_" + tableIndex;  
    }  
}  

关键突破 :通过基因嵌入,使相同用户的订单始终落在同一分片,同时支持通过订单ID直接定位分片

3 跨分片查询

3.1 异构索引表方案

picture.image

Elasticsearch索引表结构

  
{  
  "order\_index": {  
    "mappings": {  
      "properties": {  
        "order\_no": { "type": "keyword" },  
        "shard\_key": { "type": "integer" },  
        "create\_time": { "type": "date" }  
      }  
    }  
  }  
}  

4.2 全局二级索引(GSI)

  
-- 在ShardingSphere中创建全局索引  
CREATE SHARDING GLOBAL INDEX idx\_merchant ON orders(merchant\_id)   
    BY SHARDING\_ALGORITHM(merchant\_hash)   
    WITH STORAGE\_UNIT(ds\_0,ds\_1);  

4、数据迁移

双写迁移方案

picture.image

灰度切换步骤

  1. 开启双写(新库写失败需回滚旧库)
  2. 全量迁移历史数据(采用分页批处理)
  3. 增量数据实时校验(校验不一致自动修复)
  4. 按用户ID灰度流量切换(从1%到100%)

5、避坑指南

5.1 热点问题

双十一期间发现某网红店铺订单全部分到同一分片。

解决方案:引入复合分片键 (merchant\_id + user\_id) % 1024

5.2 分布式事务

这里的分布式事务使用的RocketMQ的数据最终一致性方案:

  
// 最终一致性方案  
@Transactional  
public void createOrder(Order order) {  
   orderDao.insert(order); // 写主库  
   rocketMQTemplate.sendAsync("order\_create\_event", order); // 发消息  
}  
  
// 消费者处理  
@RocketMQMessageListener(topic = "order\_create\_event")  
public void handleEvent(OrderEvent event) {  
   bonusService.addPoints(event.getUserId()); // 异步加积分  
   inventoryService.deduct(event.getSkuId()); // 异步扣库存  
}  

5.3 分页陷阱

跨分片查询页码错乱。

解决方案:改用ES聚合查询或业务折衷方案(只查最近3个月订单)。

6 终极架构方案

picture.image

性能指标

| 场景 | 拆分前 | 拆分后 | | --- | --- | --- | | 用户订单查询 | 3200ms | 68ms | | 商家订单导出 | 超时失败 | 8s完成 | | 全表统计 | 不可用 | 1.2s(近似) |

总结

  1. 分片键选择大于努力 :基因分片是订单系统的最佳拍档。
  2. 扩容预留空间 :建议初始设计支持2年数据增长。
  3. 避免过度设计 :小表关联查询远比分布式Join高。效
  4. 监控驱动优化 :重点关注分片倾斜率>15%的库。

真正的架构艺术,是在分与合之间找到平衡点。

最后欢迎加入苏三的星球,你将获得:商城微服务实战、AI开发项目课程、苏三AI项目、秒杀系统实战、商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题、面试八股文等多个优质专栏。

还有1V1答疑、修改简历、职业规划、送书活动、技术交流。

扫描下方二维码,即可加入星球(非常有价值的一次决定):

picture.image

目前星球已经更新了5200+篇优质内容,还在持续爆肝中.....

星球已经被官方推荐了3次,收到了小伙伴们的一致好评。戳我加入学习,已有1700+小伙伴加入学习。

最后推荐一下我的技术专栏《性能优化35讲》,里面包含了:接口调用、Java、JVM、并发编程、MySQL、Redis、ElasticSearch、Spring、SpringBoot等多个性能优化技巧。无论在工作,还是在面试中,都会经常遇到,非常有参考价值。

picture.image

picture.image

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 NoSQL 的实践与探索
随着 NoSQL 的蓬勃发展越来越多的数据存储在了 NoSQL 系统中,并且 NoSQL 和 RDBMS 的界限越来越模糊,各种不同的专用 NoSQL 系统不停涌现,各具特色,形态不一。本次主要分享字节跳动内部和火山引擎 NoSQL 的实践,希望能够给大家一定的启发。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论