分库分表后,运营要查"跨商家跨库的订单"
我们订单库按 user_id 哈希分了 16 个库、每库 64 张表。单用户的订单查询很顺,因为都在同一个分片。直到运营提了个需求:"给我看最近 30 天、金额大于 5000、状态是退款中的所有订单"——这个查询跨了所有库所有表,没有分片键可路由。组员在周会上问:"这种查询到底怎么搞?"我把四种常见方案摊开讲了讲。
方案一一:绑定表(Binding Table)
如果跨库查询发生在两表 Join 且分片规则一致的场景,可以用绑定表。比如订单表 t_order 和订单明细 t_order_item 都按 order_id 分片,它们必然落在同一分片,Join 不会被打散。
# ShardingSphere 配置:两表按同一分片键绑定
rules:
- !SHARDING
bindingTables:
- t_order, t_order_item
shardingAlgorithms:
order-alg:
type: INLINE
props:
algorithm-expression: t_order_${order_id % 64}
适用面窄:只解决"同分片键的 Join",对开头那个"无分片键的全局检索"没用。
方案二:广播表(Broadcast Table)
有些小表(字典表、地区表、商品类目)在每个分片都要用到,且数据量小、变更少。把它们设成广播表,每个库都存全量,Join 时本地就有:
broadcastTables:
- t_region # 地区字典,所有分片各存一份
- t_category # 类目字典
我们订单库里的"地区字典"就是广播表,运营按地区筛选时能本地 Join。但要注意:广播表一旦数据量大或变更频繁,同步成本很高,只适合小且稳的表。
方案三:异构索引(冗余分片键)
开头的全局检索,本质是"没有分片键也要查"。异构索引的思路是:再建一张按查询维度分片的索引表。比如为"按商家查订单"建一张 t_order_merchant_index,以 merchant_id 分片,只存 (merchant_id, order_id)。查某商家订单时,先走索引表拿到 order_id 列表,再用 order_id 回原表取详情。
-- 先按 merchant_id 分片路由,拿到订单主键
SELECT order_id FROM t_order_merchant_index
WHERE merchant_id = 5521 AND amount > 500000; -- 金额存索引里可过滤
-- 再用 order_id 回原分片取完整订单
SELECT * FROM t_order_${order_id % 64} WHERE order_id IN (...);
代价是写的时候要双写:订单落库同时写索引表。我们用了 ShardingSphere 的阴影库加本地事务表保证最终一致,多一次写约增加 3~5 ms。适合"读远多于写"的查询。
方案四:ES 二级索引
运营那类"多条件组合 + 范围 + 排序"的检索,最对路的还是扔给 Elasticsearch 8.x 做二级索引。订单写入 MySQL 的同时,通过 Canal 订阅 binlog 异步同步到 ES,ES 按业务查询维度建索引:
{
"mappings": {
"properties": {
"order_id": { "type": "keyword" },
"merchant_id":{ "type": "keyword" },
"amount": { "type": "scaled_float", "scaling_factor": 100 },
"status": { "type": "keyword" },
"create_time":{ "type": "date" }
}
}
}
查询直接打 ES,毫秒级返回订单主键,再回 MySQL 取详情。我们运营后台现在 90% 的复杂检索都走 ES,MySQL 只承担点查和写入。
四种方案怎么选
| 方案 | 适用场景 | 代价 |
|---|---|---|
| 绑定表 | 同分片键两表 Join | 几乎无 |
| 广播表 | 小且稳的字典表 | 写时多库同步 |
| 异构索引 | 单一新维度检索 | 双写 + 一致性 |
| ES 二级索引 | 多条件组合检索 | 引入 ES + 同步链路 |
小结
- 分库分表解决了单机容量,但把"跨分片查询"变成了新难题。
- 绑定表/广播表只解决特定 Join,救不了无分片键的全局检索。
- 异构索引适合单一新维度;ES 二级索引适合灵活组合查询,是目前运营类需求的主力。
- 任何"冗余一份"的方案都要想清楚写放大和一致性边界。
那次周会之后,我们定的原则是:点查走分片,组合检索走 ES,二者都不行的特殊 Join 才考虑异构索引。没有银弹,只有按查询模式选对路。