如何通过分页查询避免数据重复,有效提高查询速度?

更新于
2026-08-16 11:04:56
4阅读来源:SEO资源
  • 内容介绍
  • 文章标签
  • 相关推荐

遇到的痛点:

  • 查询慢: 当使用 LIMIt offset。limit 时offset 越大,MySQL 必须扫描越多行,导致响应时间成倍增长。
  • 数据重复或遗漏: 分页查询会出现同一条记录被多次返回或根本无法看到。
  • 性能瓶颈: 全表扫描、缺失索引还有无序排序都直接拖累整体吞吐量。话说回来,
  • 实现复杂度升高: 需要在业务端维护游标或状态信息。增加程序维护成本,

什么是分页查询?

Paging 是把海量数据拆分成若干页,每页只返回固定数量的数据。常见做法是使用 MySQL 的 LIMIt offset。limit,但它对大量偏移量极为低效。

如何通过分页查询避免数据重复,有效提高查询速度?

为什么分页会变慢?说起来,

  • LIMIt offset: MySQL 必须先扫描 offset 行。接下来再返回 limit 行;offset 越大,扫描成本越高。怎么说呢,
  • No index on order column: 没有覆盖索引时会触发全表扫描。怎么说呢,
  • SORT instability: 若排序字段值相同且没有唯一标识符。结果可能不确定,从而产生重复/漏掉的数据。

常见问题的观点是,数据重复与遗漏

  1. #1 排序字段不唯一: 时间戳、状态等字段往往有大量相同值;如果仅按这些字段排序,就无法保证顺序稳定。从解决办法来看,在 ORDER BY 后面追加唯一主键。如 ID DESC 或 PRIMARY_KEY ASC.
  2. #2 新增/删除导致位置变化: 当新行插入到已查询过的位置后下一个 page 就会出现重叠或缺失。说到解决办法,采用 Cursor / Keyset 分页。不再依赖 OFFSET,而是基于上一页最终一条记录的关键字段进行过滤。
  3. #3 大 OFFSET 导致全表扫描: OFFSET>10k 时几乎等价于 “SELECT * FROM table WHERE …LIMIT ,”,性能直线下降。至于解决办法,切换到游标分页或者预取主键列表 + 再回表检索。话说回来,

方案一这方面。加唯一键排序

  • 实现方式:
  • sql SELECT * FROM orders WHERE order_date>= :last_date AND id> :last_id ORDER BY order_date ASC,id ASC LIMIT :page_size;- 每次只扫描比上一次更大的 ID 范围;- 不需要 OFFSET;按理说,- 对写入频繁且需避免重复/漏查的问题尤为适用。**优点** • 单行定位,无需全表遍历; • 与 INSERT/DELETE 同步性好,可通过组合条件锁定窗口。**缺点** • 必须在业务层保存 `last_date` 与 `last_id`;• 对于完全无顺序需求的场景略显复杂。

方案二这方面,游标分页——最强防止重叠与漏查策略!

  • 主要思路:"下一次从上一次返回最终一条记录开始" 而不是用 OFFSET。可以利用数据库自身生成的游标或者自定义 token 来保持状态。
  • 典型实现: sql -- 第一次请求 SELECT * FROM products WHERE created_at <= NOW ORDER BY created_at DESC, id DESC LIMIT :page_size; -- 第二次请求传递前一次结果中的最后一条 created_at 与 id: SELECT * FROM products WHERE <= ORDER BY created_at DESC, id DESC LIMIT :page_size;
  • 优点:
  • - 完全避免因新增/删除导致的数据重叠和遗漏;
  • 缺点:- 服务端需要维护游标状态或客户端持久化 token;
  • ;- 在极高并发下需要谨慎处理事务隔离级别以确保一致性。css /* 简单演示可视化效果 */ .cursor-page { margin-bottom:12px;padding:8px,border:1px solid #ddd;} 请根据业务需求选择合适方式。

    使用索引提高速度

    • ORDER BYWHERE 子句中使用覆盖索引,例如: sql CREATE INDEX idx_orders_created ON orders;
    • 避免 SELECT *。只取需要列,减少 I/O。

    子查询 + 主键回表

    1️⃣ 提取主键列表: sql SELECT id FROM orders WHERE ... ORDER BY created_at ASC,id ASC LIMIT :page_size OFFSET :offset; ② 再根据主键回表获取完整行: sql SELECT * FROM orders WHERE id IN; - 主键已建 B‑Tree 索引,所以回表速度快。

    临时表策略

    • 对于极大数据集,可先把关键列存入临时表。再做分页: sql CREATE TEMPORARY TABLE tmp AS SELECT id FROM orders WHERE ... ORDER BY created_at,id LIMIT…,SELECT o.* FROM orders o JOIN tmp t ON o.id=t.id;
    • 临时表占内存或磁盘,可根据资源调整。说起来,

    实践案例分析

    场景 原始实现 调整后
    高频写入 + 分页读取 LIMIT offset + 全部列 Keyset 分页 + 覆盖索引
    数据量>10M 多次全表扫描 临时主键列表 + 回表
    前端无限滚动 每次请求大偏移量 游标分页 + token

    结论

    • Keyset / 游标分页 能彻底消除 “重复 & 漏掉” 痛点。并显著降低 CPU 和磁盘 I/O。话说回来,
    • 覆盖索引子查询回表 配合使用。可让每页读取仅触碰数十行,而非数百万行。

    如何通过分页查询避免数据重复,有效提高查询速度?

    1️⃣ 查询慢 → 减少 OFFSET、改用 Keyset;

    2️⃣ 数据重复 → 加唯一主键排序、使用 Cursor;

    3️⃣ 性能瓶颈 → 建覆盖索引、拆分子查询;

    4️⃣ 程序复杂度 ↑ → 用统一 token 或服务端缓存维持状态。

    遵循上述方法,你可以在保留业务灵活性的同时让分页查询跑得更快、更稳!不过,

标签:分页

遇到的痛点:

  • 查询慢: 当使用 LIMIt offset。limit 时offset 越大,MySQL 必须扫描越多行,导致响应时间成倍增长。
  • 数据重复或遗漏: 分页查询会出现同一条记录被多次返回或根本无法看到。
  • 性能瓶颈: 全表扫描、缺失索引还有无序排序都直接拖累整体吞吐量。话说回来,
  • 实现复杂度升高: 需要在业务端维护游标或状态信息。增加程序维护成本,

什么是分页查询?

Paging 是把海量数据拆分成若干页,每页只返回固定数量的数据。常见做法是使用 MySQL 的 LIMIt offset。limit,但它对大量偏移量极为低效。

如何通过分页查询避免数据重复,有效提高查询速度?

为什么分页会变慢?说起来,

  • LIMIt offset: MySQL 必须先扫描 offset 行。接下来再返回 limit 行;offset 越大,扫描成本越高。怎么说呢,
  • No index on order column: 没有覆盖索引时会触发全表扫描。怎么说呢,
  • SORT instability: 若排序字段值相同且没有唯一标识符。结果可能不确定,从而产生重复/漏掉的数据。

常见问题的观点是,数据重复与遗漏

  1. #1 排序字段不唯一: 时间戳、状态等字段往往有大量相同值;如果仅按这些字段排序,就无法保证顺序稳定。从解决办法来看,在 ORDER BY 后面追加唯一主键。如 ID DESC 或 PRIMARY_KEY ASC.
  2. #2 新增/删除导致位置变化: 当新行插入到已查询过的位置后下一个 page 就会出现重叠或缺失。说到解决办法,采用 Cursor / Keyset 分页。不再依赖 OFFSET,而是基于上一页最终一条记录的关键字段进行过滤。
  3. #3 大 OFFSET 导致全表扫描: OFFSET>10k 时几乎等价于 “SELECT * FROM table WHERE …LIMIT ,”,性能直线下降。至于解决办法,切换到游标分页或者预取主键列表 + 再回表检索。话说回来,

方案一这方面。加唯一键排序

  • 实现方式:
  • sql SELECT * FROM orders WHERE order_date>= :last_date AND id> :last_id ORDER BY order_date ASC,id ASC LIMIT :page_size;- 每次只扫描比上一次更大的 ID 范围;- 不需要 OFFSET;按理说,- 对写入频繁且需避免重复/漏查的问题尤为适用。**优点** • 单行定位,无需全表遍历; • 与 INSERT/DELETE 同步性好,可通过组合条件锁定窗口。**缺点** • 必须在业务层保存 `last_date` 与 `last_id`;• 对于完全无顺序需求的场景略显复杂。

方案二这方面,游标分页——最强防止重叠与漏查策略!

  • 主要思路:"下一次从上一次返回最终一条记录开始" 而不是用 OFFSET。可以利用数据库自身生成的游标或者自定义 token 来保持状态。
  • 典型实现: sql -- 第一次请求 SELECT * FROM products WHERE created_at <= NOW ORDER BY created_at DESC, id DESC LIMIT :page_size; -- 第二次请求传递前一次结果中的最后一条 created_at 与 id: SELECT * FROM products WHERE <= ORDER BY created_at DESC, id DESC LIMIT :page_size;
  • 优点:
  • - 完全避免因新增/删除导致的数据重叠和遗漏;
  • 缺点:- 服务端需要维护游标状态或客户端持久化 token;
  • ;- 在极高并发下需要谨慎处理事务隔离级别以确保一致性。css /* 简单演示可视化效果 */ .cursor-page { margin-bottom:12px;padding:8px,border:1px solid #ddd;} 请根据业务需求选择合适方式。

    使用索引提高速度

    • ORDER BYWHERE 子句中使用覆盖索引,例如: sql CREATE INDEX idx_orders_created ON orders;
    • 避免 SELECT *。只取需要列,减少 I/O。

    子查询 + 主键回表

    1️⃣ 提取主键列表: sql SELECT id FROM orders WHERE ... ORDER BY created_at ASC,id ASC LIMIT :page_size OFFSET :offset; ② 再根据主键回表获取完整行: sql SELECT * FROM orders WHERE id IN; - 主键已建 B‑Tree 索引,所以回表速度快。

    临时表策略

    • 对于极大数据集,可先把关键列存入临时表。再做分页: sql CREATE TEMPORARY TABLE tmp AS SELECT id FROM orders WHERE ... ORDER BY created_at,id LIMIT…,SELECT o.* FROM orders o JOIN tmp t ON o.id=t.id;
    • 临时表占内存或磁盘,可根据资源调整。说起来,

    实践案例分析

    场景 原始实现 调整后
    高频写入 + 分页读取 LIMIT offset + 全部列 Keyset 分页 + 覆盖索引
    数据量>10M 多次全表扫描 临时主键列表 + 回表
    前端无限滚动 每次请求大偏移量 游标分页 + token

    结论

    • Keyset / 游标分页 能彻底消除 “重复 & 漏掉” 痛点。并显著降低 CPU 和磁盘 I/O。话说回来,
    • 覆盖索引子查询回表 配合使用。可让每页读取仅触碰数十行,而非数百万行。

    如何通过分页查询避免数据重复,有效提高查询速度?

    1️⃣ 查询慢 → 减少 OFFSET、改用 Keyset;

    2️⃣ 数据重复 → 加唯一主键排序、使用 Cursor;

    3️⃣ 性能瓶颈 → 建覆盖索引、拆分子查询;

    4️⃣ 程序复杂度 ↑ → 用统一 token 或服务端缓存维持状态。

    遵循上述方法,你可以在保留业务灵活性的同时让分页查询跑得更快、更稳!不过,

标签:分页