SevenTnewS

分布式数据库

分布式数据库中海量订单表分页查询的五项优化经验

分布式数据库中海量订单表常面临索引选择不稳定、昂贵的回表查询以及无效分区扫描等问题。本文呈现了针对 PolarDB-X 的经过生产验证的五项优化方案,每项均附有真实性能对比数据,显示延迟从数秒降至毫秒级。

Emmanuel Fabrice Omgbwa Yasse AI 辅助

2024-08-01 · 最后更新:2026-07-17 · 阅读需 6 分钟

分布式数据库中海量订单表分页查询的五项优化经验

分页是在线应用中最常见的查询模式之一。对于小表,它很少成为性能瓶颈。但在分布式数据库的大型订单表中,分页查询面临一系列挑战:索引选择不稳定、昂贵的回表查询以及导致长尾慢查询的无效分区扫描。

本文基于实际生产案例,分享针对 PolarDB-X(阿里云分布式 SQL 数据库)中海量订单表分页查询的五项优化见解。这些策略围绕两层分区方案构建:第一层按用户 ID 进行 KEY 分区,第二层按时间字段进行 RANGE 分区, , 这是金融、电商和 SaaS 平台中常见的架构。

优化覆盖从分区裁剪、索引利用、减少回表到稳定性保证的完整链路。在深入之前需说明:本文中的表结构和 SQL 语句已脱敏,不代表真实业务数据,但性能数据来自实际用户场景。

慢分页查询的剖析

PolarDB-X 中典型的订单表很宽, , 超过 60 列, , 通常设计覆盖索引以减少回表。分区方案确保给定用户的所有数据落入相同的第一层分区(按用户 ID),然后按日期进行子分区(通过 pt 时间戳列)。

考虑一个查询,它获取大型租户最近修改的订单:

SELECT * FROM t_order
WHERE uid = 12345678
  AND pt >= '2024-08-01 00:00:00.000'
  AND update_ts > '2024-11-13 14:20:00.000'
  AND update_ts < '2024-11-13 14:25:00.000'
  AND origin != 33
ORDER BY id DESC
LIMIT 0, 100;

pt >= '2024-08-01' 条件迫使查询扫描四个子分区:p202408、p202409、p202410 和 p202411。但 update_ts 过滤器将结果限制为最近几分钟修改的订单,这些几乎总是最近的订单, , 这意味着符合条件的行都落入 p202411 分区。其他三个分区被白白扫描。在大型租户的分区上,每次无效扫描基本上会读取该分区中的所有数据,成为决定整体延迟的长尾慢查询。

经验一:根据过滤谓词进行分区裁剪

图:PolarDB-X 中优化前后的查询延迟对比
本文实际生产案例中每项技术优化前的延迟。

当过滤列与分区键相关时, , 这里,近期修改的订单往往具有较新的 pt 值, , 数据库可以利用这种相关性跳过不必要的子分区。PolarDB-X 的优化器可以通过识别 update_ts 过滤器隐含的 pt 有效范围远小于字面量 pt >= '2024-08-01' 范围,来动态裁剪分区。在实际用户场景中,此优化将查询时间从 5 秒降至 0.01 秒

经验二:根据排序列进行分区裁剪

更常见的场景是用户的 SQL 仅包含 uid 作为过滤条件, , 完全没有时间谓词。没有分区裁剪,查询必须扫描对应第一层分区下的所有子分区。在原有的合并逻辑下,所有分区的物理 SQL 语句必须完成后才能进行合并排序,这意味着最慢的分区(通常是数据量巨大的旧分区)决定了整体延迟。

PolarDB-X 通过基于排序列的动态裁剪来解决此问题。由于排序列(idupdate_ts)在时间上大致呈单调递增, , 新分区具有更高的 ID 或更晚的时间戳, , 优化器可以在找到足够行后停止扫描分区。在生产环境中,这使查询时间从 3 秒降至 0.05 秒

经验三:针对 OR/IN 条件的索引排序

行存储上快速分页的关键是提前终止:查询不应先过滤再排序以选取前 K 行,而应利用索引排序同时进行过滤和选取。等值谓词自然能做到这一点,但 OR 条件会破坏索引排序。

解决方案是将 OR/IN 条件拆分为多个有序子查询,然后合并:

SELECT * FROM (
    (SELECT * FROM t_swap WHERE asset_a = 'TOKEN26'
     ORDER BY id LIMIT 100)
    UNION
    (SELECT * FROM t_swap WHERE asset_b = 'TOKEN50'
     ORDER BY id LIMIT 100)
) t
ORDER BY id
LIMIT 100;

每个分支使用自己的索引进行提前终止。在用户场景中,这使查询时间从 10 秒降至 0.001 秒

经验四:使用延迟物化减少回表

对于宽表(超过 60 列),SELECT * 结合回表查询非常昂贵。覆盖索引有所帮助,但会消耗大量存储空间。当覆盖索引不可行时,延迟物化是答案:先通过覆盖索引仅获取主键(仅获取 LIMIT 数量的行),然后对这些少量行进行精准的主键查找。

SELECT t_order0.*
FROM (
    SELECT id, uid, pt
    FROM t_order 
    WHERE uid = 1
      AND channel_id = 0
      AND sub_id IN (0)
      AND id > 123456789
    ORDER BY update_ts DESC
    LIMIT 20
) AS t3
INNER JOIN t_order AS t_order0
  ON t3.id = t_order0.id
  AND t3.uid = t_order0.uid
  AND t3.pt = t_order0.pt
ORDER BY t_order0.update_ts DESC
LIMIT 20;

这使查询时间从 10 秒降至 0.6 秒。除了 SQL 层面的优化,PolarDB-X 还在存储引擎层面优化了回表(猜测主键页号和物理寻址),以减少 I/O 放大并提高缓存命中率。

经验五:确定性的索引选择

对于大型订单表,内存永远不够用。如果选择了错误的索引,大量数据会被加载到缓冲池中,污染缓存并触发级联故障。数据库的索引选择必须遵循与 AI 推荐索引设计一致的确定性方法论。

标准检查清单:索引排序列(避免 filesort),将 WHERE 等值列放在复合索引中 ORDER BY 列之前,尽可能使用覆盖索引,将高选择度列放在前面,避免在索引列上使用函数,并保持类型一致性。当数据库的行为与推荐的索引设计相匹配时,稳定性就有了保障。在生产环境中,PolarDB-X 保持了稳定的索引选择,而竞品分布式数据库表现出不稳定的行为, , 导致峰值 QPS 为 4K(且崩溃)对比 PolarDB-X 的 60K(线性可扩展)。

真实改进汇总表

对比项其他分布式数据库PolarDB-X
根据过滤谓词分区裁剪5 秒0.01 秒
根据排序列分区裁剪3 秒0.05 秒
索引排序(OR / UNION)10 秒0.001 秒
延迟物化10 秒0.6 秒
索引选择稳定性无保障稳定
峰值 QPS4K(扩展时崩溃)60K(线性可扩展)

核心思想可概括为:积极裁剪,利用索引排序进行提前终止,通过覆盖索引或延迟物化最小化回表,并确保索引选择的确定性。对于在分布式数据库上运行大型订单表的团队,系统应用这五项经验可以防止最常见的分页性能陷阱。

每天早晨用 3 分钟掌握科技要闻

每个工作日一封邮件,只讲真正重要的 AI 与科技动态。