深度分页慢主因是10万次随机io,覆盖索引可压至20次内;explain显示用索引却仍慢,根本原因是未实现覆盖索引——select、where、order by未被同一索引完全包含,导致回表或排序失效。

LIMIT 100000, 20 这类深度分页慢,不是因为“数据太多”,而是MySQL被迫做10万次随机IO——覆盖索引能把它压到20次以内。
为什么EXPLAIN显示用了索引却还是慢
常见错误现象:type是ref或range,但Extra列出现Using filesort、Using temporary,或者rows值远大于LIMIT指定的条数。
根本原因不是没走索引,而是没走“覆盖索引”:查询字段(SELECT)、过滤条件(WHERE)、排序字段(ORDER BY)三者没被同一个索引完全包含。
-
SELECT *几乎必然导致回表——二级索引叶子节点不含所有字段,优化器只能放弃覆盖路径 -
WHERE status = 1 ORDER BY created_at DESC,但索引建的是(created_at, status)→ 最左前缀失效,整个索引用不上 - 对索引字段用函数,比如
WHERE DATE(created_at) = '2025-01-01'→ 索引无法定位,退化为全扫描
复合索引字段顺序怎么排才真正生效
顺序不是按字母排,而是按查询执行逻辑分层:等值条件 → 范围条件 → 排序字段 → 主键(隐式)→ 其他SELECT字段。
例如查询:SELECT id, name, amount FROM orders WHERE user_id = 100 AND status = 1 ORDER BY created_at DESC
- 必须把
user_id和status放最左(等值过滤) -
created_at紧接其后(范围+排序复用) -
id不用显式写(InnoDB二级索引自带主键) -
name和amount补在末尾,确保SELECT全部覆盖
最终索引应为:CREATE INDEX idx_user_status_created ON orders (user_id, status, created_at, name, amount)。建完后用EXPLAIN确认Extra是Using index,不是Using where; Using index或空值。
子查询+JOIN写法中哪些细节会让优化直接失效
核心是“子查询只捞ID,外层精准回查”,但稍不注意就退回原形。
- 子查询里不能
SELECT *或多余字段,否则优化器大概率放弃覆盖路径 - 外层必须用
INNER JOIN ... ON t1.id = t2.id,别用WHERE t1.id IN (SELECT ...)——MySQL 5.7+仍可能转成嵌套循环 - 子查询的
ORDER BY必须和外层一致,且字段必须落在索引最右;否则Using filesort照旧 - 如果业务允许,优先用游标分页:
WHERE created_at ,配合<code>idx_user_status_created,跳过扫描,直接定位
示例正确写法:
SELECT t1.* FROM orders t1 INNER JOIN ( SELECT id FROM orders WHERE user_id = 100 AND status = 1 ORDER BY created_at DESC LIMIT 100000, 20 ) t2 ON t1.id = t2.id;
覆盖索引不是越宽越好,写入代价容易被忽略
加一个VARCHAR(200)字段进索引,B+树节点体积明显变大,buffer pool缓存效率下降,INSERT/UPDATE/DELETE都要同步更新该索引。
高频更新表上,idx_user_status_created这种五字段索引会显著拖慢写入性能。如果只是偶尔深度分页,更推荐“主键锚点+游标”方式,索引只需(user_id, status, created_at, id)四字段,兼顾读写平衡。
真正卡点永远在索引结构与查询语义的咬合度——建了索引不等于生效,EXPLAIN里的Extra列才是唯一真相。











