← 返回题目列表

分库分表后如何支持非分片键查询?什么是全局二级索引?

高频 困难 第 13 / 27 题 更新于 2026/07/28
全局二级索引非分片键查询广播查询索引表

简化版

非分片键查询如果无法路由,会退化成广播查询。常见解法是建立全局二级索引或冗余查询表,先通过非分片键查到目标分片和主键,再回主表查询详情;复杂检索可用搜索引擎或数仓承接。

详细版

以订单按 user_id 分片为例,用户查订单列表很快,但客服按 order_no、手机号、支付单号查订单时,不一定带 user_id。解决方案包括:

  1. 在订单号中编码分片信息,让订单号可直接路由;
  2. 建全局索引表:order_no -> user_id/shard_id/order_id
  3. 建冗余查询表:按手机号、支付单号等维度组织数据;
  4. 使用 Elasticsearch 承接多条件检索;
  5. 低频后台查询允许受控广播,但要限流和审计。

全局二级索引的难点是和主表一致性:主表写成功索引写失败、索引延迟、重复写、删除更新同步都要处理,通常通过本地消息、事务消息、补偿任务和对账兜底。

完整版教学

一、非分片键查询为什么麻烦

分库分表后的理想查询是带分片键的查询。比如订单按 user_id 分片,用户查自己的订单时带 user_id,路由层可以直接定位到一个分片。

但业务不会永远这么配合。客服可能按订单号查,风控可能按手机号查,财务可能按支付单号查。如果这些字段不能推导出分片位置,系统不知道数据在哪里,只能访问所有分片。这就是广播查询。

广播查询的问题不是“多查几次”这么简单。分片数越多,请求放大越严重;某个慢分片会拖慢整体响应;数据库连接数和线程池会被占满;如果后台人员批量查询,还可能影响在线用户链路。

二、全局二级索引的基本思路

全局二级索引类似一张路由辅助表。它不一定保存完整数据,只保存“非分片键到主表位置”的映射。例如:

order_no -> user_id, order_id, shard_id
pay_no   -> user_id, order_id, shard_id
phone    -> user_id list 或 order_id list

查询流程变成两步:先查索引表,拿到主表分片位置;再去主表精准查询。这样可以把原来的全分片广播变成一次索引查询加一次定点查询。

全局索引可以放在独立库、搜索引擎、KV 存储或专门的索引服务中。选型取决于查询复杂度、实时性要求和一致性要求。

三、索引一致性是最难的部分

写订单时,主表和全局索引不是同一张表,甚至不在同一个库。可能出现这些异常:

  • 主表写成功,索引写失败,导致按订单号查不到;
  • 索引写成功,主表写失败,导致查到脏索引;
  • 订单状态更新了,索引里的摘要字段没更新;
  • 删除或取消订单后,索引仍然存在旧记录;
  • 重试写索引产生重复记录。

处理方式通常是“本地事务 + 消息 + 幂等 + 对账”。主表本地事务内写一条索引构建消息,消息消费者异步更新索引;索引记录使用唯一键保证幂等;后台任务定期扫描主表和索引差异。对实时性要求很高的点查索引,也可以让主流程同步写索引,但仍要有失败补偿。

四、订单号编码分片信息是一种常见优化

如果业务允许,可以在全局 ID 中嵌入分片信息。例如订单号由时间、机器号、序列号、分片号组成。拿到订单号后,路由层可以解析出目标分片,不需要查索引表。

这种方式性能很好,但要提前设计。订单号一旦对外暴露,格式就很难改变。还要避免泄露过多业务信息,比如订单量、机器数量、分片规模等。如果担心可猜测,可以做混淆或只嵌入必要路由位。

五、复杂查询不要硬塞给分片库

后台多条件查询,比如“某地区、某时间段、某状态、某手机号尾号的订单”,很难靠一个全局索引解决。此时更适合用 Elasticsearch、ClickHouse、数据仓库或专门的运营查询平台。

在线分片库应该服务核心交易链路:点查、列表、状态更新。复杂检索和报表分析如果都压到分片库上,会让分库分表体系变得很脆弱。

六、全局索引表的写入流程要设计成可恢复

全局二级索引最难的不是查询,而是写入一致性。以订单主表和 order_no -> shard_id 索引表为例,创建订单时如果先写索引再写主表,主表失败会留下脏索引;如果先写主表再写索引,索引失败会导致按订单号查不到。两边不在同一个本地事务里,就必须设计状态和补偿。

常见做法是主表事务内写一条待构建索引的本地消息,事务提交后异步消费者构建索引。索引表使用唯一键保证幂等,消费者失败可以重试。查询时如果索引短暂不存在,可以提示稍后再试、降级到受控广播,或者只对核心点查做同步索引写入。

如果索引表本身也分片,要保证相同索引键稳定路由到同一分片。比如手机号唯一索引按手机号 Hash,订单号索引按订单号 Hash。索引分片和主表分片可以不同,因为索引的职责是定位主表位置。

七、索引数据要有生命周期和一致性校验

全局索引不是写完就结束。订单取消、用户换手机号、支付单状态变化,都可能要求更新或删除索引。如果索引记录长期不清理,会造成查到旧位置、查到已删除业务对象、索引表膨胀等问题。

因此索引表要设计字段:索引键、主键、分片位置、状态、版本号、更新时间。更新时用版本号防止乱序消息覆盖新值。删除时可以先标记失效,再异步清理,避免查询和删除并发导致短暂找不到。

还要做对账。可以定期从主表抽样或扫描增量数据,校验索引是否存在、位置是否正确、状态是否一致。全局索引是“地图”,地图错了比没有地图更危险,因为请求会被精准带到错误地方。面试里讲出索引生命周期和对账,答案会明显更工程化。

八、常见误区与追问

这道题要紧扣「全局二级索引」本身回答,不能把它混成泛泛的分库分表套话。面试官通常不是只听定义,而是看你能不能把适用场景、关键流程、失败边界和工程取舍串起来,尤其要说明拆分边界、路由规则、扩容迁移、跨库查询和一致性兜底。

回答层次要讲清的内容容易漏掉的边界
核心结论全局二级索引用非分片键建立到真实分片位置的映射,用额外写入和一致性维护换非分片键查询能力不要停在名词解释
流程机制写主表生成记录 -> 写全局索引映射 -> 按索引定位分片 -> 回表查询真实数据 -> 异步修复索引不一致 -> 定期对账清理脏索引要说清触发点、状态变化、确认点和失败兜底
工程取舍用户按手机号查订单时,索引表可存 phone -> order_id/shard_id/table_id,避免扫描 64 个分片分库分表能扩展容量和吞吐,但会带来路由、事务、Join、唯一约束、扩容和运维复杂度
全局二级索引 面试拆解:
1. 写主表生成记录
2. 写全局索引映射
3. 按索引定位分片
4. 回表查询真实数据
5. 异步修复索引不一致
6. 定期对账清理脏索引

记忆钩子:先给结论,再拆流程,再讲数字例子和失败边界;回答「全局二级索引」时要围绕题目问法收束,不要把相邻概念堆成一段没有重点的名词清单。

  • 误区:全局二级索引和单库普通索引一样。 它通常是跨分片维护的额外数据结构,写路径和一致性成本更高。
  • 误区:建了全局索引就不需要分片键。 高频主链路仍应优先带分片键,全局索引适合少量辅助查询。
  • 误区:索引表一定强一致。 很多系统用最终一致,需要处理索引缺失、脏索引和回表失败。
  • 追问:全局索引写失败怎么办? 通过事务消息、重试表、binlog 订阅或对账任务补偿。
  • 追问:为什么要回表? 索引只存定位信息或少量字段,真实业务数据仍在原分片。
  • 追问:索引热点如何处理? 对热点键拆桶、限流、缓存或改变索引维度。

九、加强记忆

非分片键查询的本质是“我知道业务条件,但不知道数据在哪”。全局二级索引就是一张位置地图,先把非分片键翻译成分片位置,再回主表查。它能避免广播,但必须认真处理索引和主表之间的一致性、幂等、延迟和对账。