场景痛点:某电商平台的MySQL订单表达到7亿行时,出现致命问题:
-- 简单查询竟需12秒!
SELECT * FROM orders WHERE user_id= LIMIT ;
-- 统计全表耗时278秒
SELECT COUNT(*) FROM orders;
核心矛盾:
关键认知:当单表数据量突破5000万行时,就该启动分库分表设计预案。
那么问题来了,假如现在有10亿的订单数据,我们该如何做分库分表呢?
今天这篇文章就跟大家一起聊聊这个问题,希望对你会有所帮助。
优化效果:
分片键选择三原则:
分片策略对比:
策略类型 | 适用场景 | 扩容复杂度 | 示例 |
---|---|---|---|
范围分片 | 带时间范围的查询 | 简单 | create_time按月分表 |
哈希取模 | 均匀分布 | 困难 | user_id % 128 |
一致性哈希 | 动态扩容 | 中等 | 使用Ketama算法 |
基因分片 | 避免跨分片查询 | 复杂 | 从user_id提取分库基因 |
针对订单系统的三大高频查询:
解决方案:
Snowflake订单ID改造:
// 基因分片ID生成器
publicclass OrderIdGenerator {
// 64位ID结构:符号位(1)+时间戳(41)+分片基因(12)+序列号(10)
privatestaticfinalint GENE_BITS = ;
public static long generateId(long userId) {
long timestamp = System.currentTimeMillis() - 1288834974657L;
// 提取用户ID后12位作为基因
long gene = userId & (( << GENE_BITS) - );
long sequence = ... // 获取序列号
return (timestamp << )
| (gene << )
| sequence;
}
// 从订单ID反推分片位置
public static int getShardKey(long orderId) {
return (int) ((orderId >> ) & 0xFFF); // 提取中间12位
}
}
路由逻辑:
// 分库分表路由引擎
publicclass OrderShardingRouter {
// 分8个库 每个库16张表
privatestaticfinalint DB_COUNT = ;
privatestaticfinalint TABLE_COUNT_PER_DB = ;
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直接定位分片
Elasticsearch索引表结构:
{
"order_index": {
"mappings": {
"properties": {
"order_no": { "type": "keyword" },
"shard_key": { "type": "integer" },
"create_time": { "type": "date" }
}
}
}
}
-- 在ShardingSphere中创建全局索引
CREATE SHARDING GLOBAL INDEX idx_merchant ON orders(merchant_id)
BY SHARDING_ALGORITHM(merchant_hash)
WITH STORAGE_UNIT(ds_0,ds_1);
双写迁移方案:
灰度切换步骤:
双十一期间发现某网红店铺订单全部分到同一分片。
解决方案:引入复合分片键 (merchant_id + user_id) % 1024
这里的分布式事务使用的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()); // 异步扣库存
}
跨分片查询页码错乱。
解决方案:改用ES聚合查询或业务折衷方案(只查最近3个月订单)。
性能指标:
场景 | 拆分前 | 拆分后 |
---|---|---|
用户订单查询 | 3200ms | 68ms |
商家订单导出 | 超时失败 | 8s完成 |
全表统计 | 不可用 | 1.2s(近似) |
真正的架构艺术,是在分与合之间找到平衡点