惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
月光博客
月光博客
Y
Y Combinator Blog
WordPress大学
WordPress大学
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
美团技术团队
T
Tailwind CSS Blog
小众软件
小众软件
量子位
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
有赞技术团队
有赞技术团队
P
Proofpoint News Feed
G
Google Developers Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Recent Announcements
Recent Announcements
The GitHub Blog
The GitHub Blog
博客园 - 三生石上(FineUI控件)
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

博客园 - 幽州散人

SQL优化案例 synchronized 为啥会让虚拟线程卸载失效 主线任务和使命 温州3日之旅 CompletableFuture + 异步Servlet:同步的姿势、异步的效果 Snowflake雪花算法与发号器的应用 windows Java 幽灵转发壳进程 SHOW SESSION STATUS 与 Handler 变量 MySQL 5.7 Profiles 执行性能分析 MySQL临时表与文件排序 MySQL 5.7 MRR多范围读优化 MySQL 5.7 ICP索引条件下推优化 MySQL符合索引与最左前缀原则 从I/O 的物理成本理解回表和索引覆盖 第4个电瓶以及补漆 Flink原理:并行度与keyGroup桶 什么是数字批发银行 Java虚拟线程(三)实现原理 Java虚拟线程(二) Java虚拟线程(一) 大模型推理层服务化架构 传奇调查员之路:理智值-99,但我必须听懂深渊的语言 js里调用智能合约读/写函数的方法的区别 字节与其16进制字符表示转换的bug Redisson分布式锁 交易心得 DexScreener接口初探 某安全软件跑飞了。。 Ed25519算法签名与验签的Java实现 Tendermint拜占庭容错引擎
也谈MySQL limit offset深翻页问题
幽州散人 · 2026-08-19 · via 博客园 - 幽州散人

深翻页问题

一页10条记录,翻第1页和第50000页的区别

-- ===== 步骤1:浅分页(第1页) → 很快 =====
EXPLAIN
SELECT * FROM orders
ORDER BY id
LIMIT 10 OFFSET 0;  -- 或 LIMIT 0, 10

id|select_type|table |partitions|type |possible_keys|key    |key_len|ref|rows|filtered|Extra|
--|-----------|------|----------|-----|-------------|-------|-------|---|----|--------|-----|
 1|SIMPLE     |orders|          |index|             |PRIMARY|8      |   |  10|   100.0|     |

-- 3ms
SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 0;

-- ===== 步骤2:深分页(第5万页) → 非常慢! =====
EXPLAIN
SELECT * FROM orders
ORDER BY id
LIMIT 10 OFFSET 500000;

id|select_type|table |partitions|type |possible_keys|key    |key_len|ref|rows  |filtered|Extra|
--|-----------|------|----------|-----|-------------|-------|-------|---|------|--------|-----|
 1|SIMPLE     |orders|          |index|             |PRIMARY|8      |   |500010|   100.0|     |

SELECT * FROM orders ORDER BY id LIMIT 10 OFFSET 500000;
-- 411ms 慢!但为什么慢?

存储引擎去聚集索引里边扫500000 + 10 行,然后逐行返给Server,Server按照LIMIT 10 OFFSET 500000判断不在区间的就丢弃,在则保留。最后只留10行。
在上面执行计划可以看到rows确实是500010 ,就这就是深翻页问题的原因。

优化方法

-- =====  优化方案 —— 延迟关联 =====
-- 先只查主键(覆盖索引),再用主键回表
-- 方案A:子查询法
EXPLAIN
SELECT * FROM orders
WHERE id >= (
    SELECT id FROM orders ORDER BY id LIMIT 1 OFFSET 500000
)
ORDER BY id LIMIT 10;

id|select_type|table |partitions|type |possible_keys|key    |key_len|ref|rows  |filtered|Extra      |
--|-----------|------|----------|-----|-------------|-------|-------|---|------|--------|-----------|
 1|PRIMARY    |orders|          |range|PRIMARY      |PRIMARY|8      |   |497542|   100.0|Using where|
 2|SUBQUERY   |orders|          |index|             |PRIMARY|8      |   |500001|   100.0|Using index|

-- 91ms
SELECT * FROM orders
WHERE id >= (
    SELECT id FROM orders ORDER BY id LIMIT 1 OFFSET 500000
)
ORDER BY id LIMIT 10;


-- 方案B:JOIN 法(更通用)
EXPLAIN
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 10 OFFSET 500000
) t ON o.id = t.id;

id|select_type|table     |partitions|type  |possible_keys|key    |key_len|ref |rows  |filtered|Extra      |
--|-----------|----------|----------|------|-------------|-------|-------|----|------|--------|-----------|
 1|PRIMARY    |<derived2>|          |ALL   |             |       |       |    |500010|   100.0|           |
 1|PRIMARY    |o         |          |eq_ref|PRIMARY      |PRIMARY|8      |t.id|     1|   100.0|           |
 2|DERIVED    |orders    |          |index |             |PRIMARY|8      |    |500010|   100.0|Using index|

-- 92ms
SELECT o.* FROM orders o
INNER JOIN (
    SELECT id FROM orders ORDER BY id LIMIT 10 OFFSET 500000
) t ON o.id = t.id;

上面优化方法的思路都是属于先用LIMIT 10 OFFSET 500000定位主键,然后再用主键去回表。
从iops角度看,其实没有减少,都是扫了500010行,但是优化方法第一步只查id, 而未优化方法返回的是完整行。

原始深分页:每行解包完整记录(12 个字段、约 120 字节)→ 传完整行 → Server 丢弃
延迟关联:  每行只提取 8 字节主键                    → 传 8 字节  → Server 丢弃
            ↑
         同样的 50 万次"读页",但每行的解包和传输工作量差一个量级

这就是 411ms 和 91ms 之间那 320ms 的构成——不是少读了页,是每行少干了解包完整记录的活。iops没省,但省了CPU的力气