真实面经题目 · 原创解析
分表规则中,跨表查询+分页该怎么做?
跨表查询和分页的核心不是把单表 limit offset 原样套到每个分表,而是先判断能否命中分片键;能命中就路由到单表或少量表,不能命中才考虑广播查询、局部排序、全局归并。深分页会被分片数放大,工程上更推荐游标分页、二级索引表、异步宽表或搜索服务承接全局查询。
真实面经题目 · 原创解析
跨表查询和分页的核心不是把单表 limit offset 原样套到每个分表,而是先判断能否命中分片键;能命中就路由到单表或少量表,不能命中才考虑广播查询、局部排序、全局归并。深分页会被分片数放大,工程上更推荐游标分页、二级索引表、异步宽表或搜索服务承接全局查询。
我会先把问题分成两类:能命中分片键和不能命中分片键。能按 user_id、tenant_id、seller_id 等分片键查询时,路由层应该定位到单个分表或少量分表,然后在目标表内正常 order by、limit。不能命中分片键时,本质是跨分片全局排序分页,不能每张表都 limit 20 后简单拼接,否则全局顺序和分页边界都会错。常见做法是查询网关或中间件广播到相关分表,每个分表按同一排序条件取候选集,再在中间层做多路归并排序,最后截取目标页。但 offset 越大代价越高,例如第 100 页每页 20 条,可能每个分片都要取 2000 条再归并。线上更推荐 seek 分页,用 last_time、last_id 这类稳定游标继续查询;或者建设二级索引表、冗余索引表、异步宽表、搜索服务,把全局检索提前组织好。还要在业务侧限制时间范围、最大页数和异步导出,避免 OLTP 分表承接无界扫描。
跨表分页的第一步不是讨论怎么 merge,而是确认查询条件能否命中分片键。请求带 user_id、tenant_id、seller_id 等路由字段时,查询层应直接定位到目标分表,只查一张或少量几张表。能通过接口参数、登录态或业务上下文补齐分片键,就不应退化成全分片广播。
如果查询条件不包含分片键,例如运营后台按状态查所有订单,或全局按手机号、时间、关键字检索,就进入跨分片路径。查询网关或中间件把 SQL 改写并广播到相关分表,每个分表独立过滤、局部排序和取候选。广播要有限制,只适合低频、窄时间范围、浅分页场景。
跨表分页难点在全局排序,而不是每张表各自排序后拼接。正确方式是每个分片按同一排序字段取候选集,例如 create_time desc、id desc,再由查询层做多路归并,从每个分片的有序结果中选出全局前 N 条。排序字段要稳定,通常加唯一 ID 做第二排序键。
传统 limit offset 在单表里已有深分页问题,放到分表会更严重。要查 offset 10000、limit 20,并且有 32 个分表,为保证全局第 10001 到 10020 条正确,每个分片理论上可能都要取 offset+limit 条候选。数据库扫描、网络传输、中间层内存和归并 CPU 都会被放大。
工程上更推荐 seek pagination。接口不传 pageNo 和 offset,而传上一页最后一条的排序游标,例如 last_create_time 和 last_id。下一页查询变成 create_time 更小,或时间相等且 id 更小。这样不需要跳过大量历史记录,适合订单流水、消息列表、信息流等连续翻页场景。
如果业务必须按手机号、订单号、状态、时间等非分片键查询,需要建立二级索引表或冗余索引表。索引表按查询维度重新组织,保存主键、路由键和排序字段;查询先拿到 ID 和分片键,再回源真实分表。代价是写入链路更复杂,需要处理一致性、补偿和回查。
多条件筛选、模糊搜索、相关性排序、聚合统计不适合让 OLTP 分表硬扛。更常见做法是异步同步到搜索服务、ClickHouse 或专门宽表,查询侧返回 ID 列表,再按需回源校验。这里要说明最终一致性、权限过滤、删除同步和关键状态回源校验。
稳定方案通常还包括产品和接口层约束:后台列表强制最近 7 天或 30 天,限制最大页数;用户端只提供下一页,不支持跳到第 500 页;大范围导出走异步任务;核心交易查询必须带分片键。这样才能让分库分表系统服务高并发交易,而不是退化成全局扫描数据库。
每张表的前 20 条不等于全局前 20 条,某个分表可能贡献了全局前几十条数据。如果每张表只取固定 20 条再拼接,排序结果和分页边界都可能错误。正确做法是每个分表取足够候选,再按全局排序键归并后截取当前页。
单表 offset 需要扫描并丢弃前面的记录,跨分片时为了得到全局第 N 页,每个分片都可能需要取 offset+limit 条候选。分片数越多,总候选量越大,大量数据在归并后被丢弃,放大扫描、网络、内存和 CPU。
游标必须来自稳定排序键,常见组合是 create_time 加 id。第一页按 create_time desc、id desc 查询,下一页带上一页最后一条的 create_time 和 id,条件改成 create_time 更小,或时间相等且 id 更小。唯一 ID 负责处理时间相同的 tie-breaker。
查询条件相对固定,例如手机号查订单、用户维度查交易、按日期查流水,适合二级索引表或冗余索引表。条件灵活、包含模糊搜索、多字段组合、相关性排序时,搜索服务更合适。但搜索侧通常最终一致,关键结果要回源校验。
中间件可以处理路由、SQL 改写、广播、结果归并和排序合并,降低业务代码复杂度,但不能消除物理成本。深分页仍会带来大量扫描和归并开销,所以仍要限制页数、控制范围,并为高频查询建设索引表或宽表。
先判断这是在线强需求还是后台低频需求。在线接口通常不建议支持任意深页跳转,可以改成下一页或限制最大页数。后台场景可以强制时间范围、异步导出、走宽表或分析库;如果必须查交易库,要加限流、超时、最大 offset 和审计。