加索引、用覆盖索引、加时间范围过滤是优化order by + limit查询的三大关键:需为created_at建降序索引,只查必要字段或构建覆盖索引,并添加where created_at > now() - interval '7 days'等可命中索引的时间条件。

ORDER BY + LIMIT 是最直接的写法,但不加索引会全表扫描
在千万级表里执行 SELECT * FROM orders ORDER BY created_at DESC LIMIT 100,如果 created_at 没有索引,MySQL 或 PostgreSQL 都会先排序全部千万行,再取前100——这可能耗时数秒甚至分钟。真实场景中,用户等不到结果就刷新了。
实操建议:
- 确认排序字段是否已建索引:
SHOW INDEX FROM orders WHERE Key_name = 'idx_created_at';(MySQL)或\d orders(PostgreSQL) - 若无索引,优先添加:
CREATE INDEX idx_created_at ON orders(created_at DESC); - 注意:
DESC在 MySQL 8.0+ 和 PostgreSQL 中可被索引原生支持;老版本 MySQL 只能升序索引 + 逆序读取,效果打折扣
用覆盖索引避免回表,进一步提速
即使有 created_at 索引,如果查询字段很多(比如 SELECT *),数据库仍要根据索引中的主键 ID 回到聚簇索引查完整行,IO 开销大。
实操建议:
- 只查真正需要的字段,例如:
SELECT id, user_id, amount, created_at FROM orders ORDER BY created_at DESC LIMIT 100 - 把常用查询字段加入索引,构成覆盖索引:
CREATE INDEX idx_created_at_cover ON orders(created_at DESC, id, user_id, amount); - 覆盖索引让查询完全走索引 B+ 树,不访问数据页,通常能从 200ms 降到 20ms 以内
时间范围过滤比纯 LIMIT 更可靠
业务上“最新100条”往往隐含“最近N天内”,但仅靠 LIMIT 无法保证时效性——万一最近一天没新数据,返回的可能是半年前的记录。
实操建议:
- 加
WHERE created_at > NOW() - INTERVAL 7 DAY(MySQL)或WHERE created_at > NOW() - INTERVAL '7 days'(PostgreSQL) - 该条件必须命中索引,所以
created_at字段仍需索引,且不能对字段做函数操作(如DATE(created_at)会失效) - 如果严格要求“绝对最新100条”,再补一层
ORDER BY created_at DESC LIMIT 100即可
分页深度大时,OFFSET 不是问题,但别误用
有人担心 LIMIT 100 OFFSET 1000000 会慢——确实会,但本题只要前100条,OFFSET 0 实际无开销。真正要注意的是别把“前100条”错写成 LIMIT 100 OFFSET 1 或漏写 LIMIT 导致全量返回。
实操建议:
-
LIMIT 100等价于LIMIT 100 OFFSET 0,无需显式写OFFSET - 检查应用层拼 SQL 是否动态注入了错误 offset,尤其在分页组件复用时
- 用
EXPLAIN确认执行计划里rows显示扫描行数是否接近100(理想),而不是几百万
索引字段的选择、是否覆盖、时间条件是否可下推——这三个点卡住了绝大多数“为什么加了 LIMIT 还很慢”的问题。实际调优时,先看 EXPLAIN,再看索引定义,最后看 WHERE 条件有没有破坏索引使用。











