题目:分库分表之后的怎么进行 join 操作
难度:medium
标签:db/sharding, general/knowledge, difficulty/medium
评分:⭐⭐⭐☆☆(3/5)
分库分表之后,常规的数据操作方式基本上不是照着了。要么是大宽表,把数据聚合到一个表中,一次性查询出来。要么是读取到要查的数据,通过接口去调三方服务,调其他服务提供的数据去查一遍。这个查的过程中可能会设置缓存,这个缓存直接从缓存中读取,而不是每次去扫表来提升效率,降低消耗的资源跟时间。我们之前做的服务就是这个样子的。然后主表去查数据,然后数据中需要的外部的表的资源去,可能本身不是当前库内的,直接是其他库中的。这时候去调其他库的服务,通过服务的提供的接口,然后我们的主件 ID 去调对应服务的接口,获取到服务的数据。这些数据确认它的时效性,如果时效性相对较长的话,会设设置一个较长的缓存,然后把这个数据直接存起来。如果它数据的缓存性比较短,这时候可能就让三方让对应的服务去自己内部设缓存不过基本上本地也会设二级缓存,因为毕竟调三方的时候也要走网络的服务。另外是调三方的时候,让三方基本上会,如果是列表性的,会让三方提供批量的接口,而不是一条数据一条数据地查。降低网络的请求次数,提升接口的质量跟返回结果的速度。
优点:
不足:
补充:
核心考点定位:能否系统掌握分片场景下 JOIN 的六种方案,并说清适用场景和代价。
知识点拆解:
| 方案 | 复杂度 | 一致性 | 适用规模 |
|---|---|---|---|
| 绑定表 | 低 | 强 | 中 |
| 广播表 | 低 | 中(异步) | 小表 |
| 异构索引表 | 中 | 强(双写) | 中 |
| 大宽表 | 低 | 强 | 大 |
| 服务化 JOIN | 高 | 中(缓存) | 大 |
| ES/ClickHouse | 高 | 弱(异步) | 超大 |
考察方向:
延伸递进面试题:
面试官您好,跨分片 JOIN 是分库分表的最大架构限制——中间件要把 N×M 分片的数据拉到内存做笛卡尔积,性能随分片数急剧下降。所以生产环境的思路是能不用 JOIN 就不用,能本地 JOIN 就不跨分片。我一般用六种方案来拆解。
方案一:绑定表(Binding Table)。这是 ShardingSphere、MyCat 都支持的特性。核心思路是主子表用同一分片键——比如订单主表和订单明细表都按 user_id 分片,这样同一用户的所有订单和明细物理上就在同一个分片里。中间件识别到绑定关系后自动做本地 JOIN,性能接近单库 JOIN。这是分片场景最理想的方案,但要求业务侧设计时提前对齐分片键。
方案二:广播表(Broadcast Table)。字典类小表(用户等级、商品类目、租户配置、地区码)全量复制到每个分片。JOIN 完全本地,性能 = 单库 JOIN。代价是小表更新要广播到所有分片,一般用消息队列异步同步。适合万级以下的小表。100w 条的字典就不适合了。
方案三:异构索引表。当主表按 user_id 分片,但业务需要按 order_id 查时,建一张异构表 order_index(order_id, user_id) 按 order_id 分片。查订单详情时先查异构表拿到 user_id,再回主表精确定位。两次查询拼接替代跨分片 JOIN。写路径要双写保证一致性,通常用同事务写入 + 本地消息表 + 定时对账修复。
方案四:大宽表(反范式)。把需要的字段直接冗余到主表。优点是一次查询出全部,缺点是写放大——更新要改多份、字段冗余、维护成本高。适合读多写少场景,比如订单列表页要展示订单状态 + 商品名 + 用户昵称,直接冗余进订单表。写多读少的场景(比如高频更新的库存)不适合。
方案五:服务化 JOIN。跨域强耦合场景,比如订单服务查用户服务拿用户信息。这个方案有三大优化必须做:
方案六:ES / ClickHouse。宽表同步到搜索引擎或 OLAP 库,复杂多表 JOIN、聚合查询全走离线库。代价是数据一致性(异步同步有延迟),但换来的是查询性能提升 10-100 倍。适合搜索、后台管理、报表这类场景。
电商订单表典型组合:
一句话总结:分片后的 JOIN 没有通用解,核心原则是先设计好分片键让 JOIN 天然本地,实在不行再用异构表拼接,最后才是服务化 JOIN。反范式和 ES 是兜底手段,不是首选。