SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;的庖丁解牛
深度分页必用游标方案确保排序字段是聚簇索引用EXPLAIN验证执行计划(type=range)因为最好的分页,不是跳过百万行,而是精准定位下一程。
·
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10; 是 高性能分页查询的典范,它通过 游标分页(Cursor-based Pagination) 避免了传统 OFFSET 的性能陷阱。
一、执行机制:为什么高效?
▶ 1. 执行计划解析
EXPLAIN SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;
- 关键输出:
type: range key: PRIMARY rows: 10 Extra: Using where; Using index
▶ 2. 执行流程
- 核心优势:
- 仅扫描 10 行(而非
OFFSET的 1,000,010 行) - 直接利用聚簇索引(InnoDB 主键即数据存储顺序)
- 仅扫描 10 行(而非
💡 核心认知:
WHERE id > N+ORDER BY id= 直接跳转到 N+1 位置
二、索引利用:聚簇索引的威力
▶ 1. InnoDB 聚簇索引结构
- 数据存储:
- 主键索引的叶子节点 = 完整行数据
- 物理存储按主键顺序排列
- 查询优势:
WHERE id > N→ 直接定位到 B+ 树的 N+1 位置- 顺序读取后续 10 行 → 无随机 I/O
▶ 2. 对比非聚簇索引
- MyISAM 表:
- 主键索引 ≠ 数据存储顺序
- 需额外回表 → 性能下降
- InnoDB 非主键查询:
- 若
id非主键 → 需回表 → 性能下降
- 若
📌 关键点:
此查询高效的前提是id为 InnoDB 聚簇索引(通常是自增主键)
三、工程价值:游标分页的实践
▶ 1. 前端交互设计
- 传递游标:
- 前端保存上一页最后一条的
id - 下一页请求携带
cursor=id
- 前端保存上一页最后一条的
- 示例 API:
GET /orders?cursor=1000000&limit=10
▶ 2. 后端实现
// Laravel 示例
public function index(Request $request)
{
$cursor = $request->input('cursor', 0);
$limit = min($request->input('limit', 10), 100);
$orders = Order::where('id', '>', $cursor)
->orderBy('id')
->limit($limit)
->get();
return response()->json([
'data' => $orders,
'next_cursor' => $orders->last()?->id
]);
}
▶ 3. 性能对比
| 查询方式 | 扫描行数 | 响应时间(1亿行表) |
|---|---|---|
LIMIT 1000000, 10 |
1,000,010 | 12.3 秒 |
| 游标分页 | 10 | 0.008 秒 |
四、避坑指南
| 陷阱 | 破局方案 |
|---|---|
| id 非自增主键 | 确保排序字段是聚簇索引 |
| 并发插入导致漏数据 | 接受最终一致性(业务可容忍) |
| 反向分页困难 | 单独实现 WHERE id < cursor ORDER BY id DESC |
五、终极心法
**“游标不是技巧,
而是索引的舞蹈——
- 当你 利用聚簇,
你在消除随机;- 当你 传递锚点,
你在跳过扫描;- 当你 接受最终一致,
你在拥抱现实。真正的分页优化,
始于对存储的敬畏,
成于对细节的精控。”
结语
从今天起:
- 深度分页必用游标方案
- 确保排序字段是聚簇索引
- 用
EXPLAIN验证执行计划(type=range)
因为最好的分页,
不是跳过百万行,
而是精准定位下一程。
更多推荐



所有评论(0)