游标具象化的庖丁解牛
深度分页必用游标方案复合排序末尾加主键用EXPLAIN验证执行计划(type=range)因为最好的分页,不是跳过百万行,而是精准定位下一程。
·
“游标具象化” 是数据库分页优化中的核心概念,但其本质常被误解为“指针”或“位置标记”。实际上,游标(Cursor)在工程实践中是“可比较的排序字段值”,通过 记录上一页最后一条数据的排序键,实现高效、无偏移的分页。
一、核心原理:游标 ≠ 指针,而是“排序锚点”
▶ 1. 传统 OFFSET 的缺陷
-- 跳过 100 万行 → 扫描 1,000,010 行
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;
- 问题:
- 必须扫描
offset + size行 - 成本随
offset线性增长
- 必须扫描
▶ 2. 游标分页的本质
-- 记录上一页最后 id=1000000
SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;
- 关键:
- 游标 = 排序字段的值(如
id=1000000) - 不是物理位置,而是逻辑排序锚点
- 游标 = 排序字段的值(如
💡 核心认知:
游标是“上次看到的最大值”,而非“跳过的行数”
二、工程实现:四类游标场景
▶ 场景 1:单字段主键(最简单)
- 表结构:
CREATE TABLE orders ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id INT, amount DECIMAL(10,2) ); - 分页逻辑:
// 第一页 $lastId = 0; $rows = DB::select("SELECT * FROM orders WHERE id > ? ORDER BY id LIMIT 10", [$lastId]); // 下一页(取最后一条的 id) $lastId = end($rows)->id;
▶ 场景 2:多字段排序(复合游标)
- 需求:按
user_id ASC, created_at DESC分页 - 表结构:
CREATE TABLE logs ( id BIGINT, user_id INT, created_at DATETIME, INDEX idx_user_time (user_id, created_at) ); - 分页逻辑:
// 上一页最后一条:user_id=123, created_at='2023-01-01 10:00:00' $rows = DB::select(" SELECT * FROM logs WHERE (user_id > ?) OR (user_id = ? AND created_at < ?) ORDER BY user_id ASC, created_at DESC LIMIT 10 ", [123, 123, '2023-01-01 10:00:00']);
▶ 场景 3:非唯一排序字段(需主键兜底)
- 问题:
created_at可能重复 → 游标失效
- 解决方案:
-- 添加主键作为 tie-breaker SELECT * FROM logs WHERE (created_at, id) > ('2023-01-01 10:00:00', 1000) ORDER BY created_at, id LIMIT 10;
▶ 场景 4:反向分页(上一页)
- 逻辑:
-- 上一页:小于当前最小值 SELECT * FROM orders WHERE id < ? ORDER BY id DESC LIMIT 10;
三、避坑指南:游标的五大陷阱
| 陷阱 | 破局方案 |
|---|---|
| 忽略排序字段唯一性 | 复合排序时,末尾加主键确保唯一性 |
| 错误处理 NULL 值 | WHERE col > ? 会跳过 NULL → 改用 WHERE (col > ? OR col IS NULL) |
| 并发插入导致漏数据 | 游标分页无法保证强一致性 → 接受最终一致性 |
| 未使用覆盖索引 | 确保 WHERE + ORDER BY 字段有联合索引 |
| 前端传递游标被篡改 | 对游标值签名(如 JWT)或仅允许顺序翻页 |
四、性能对比:游标 vs OFFSET
| 指标 | OFFSET (1M, 10) | 游标分页 |
|---|---|---|
| 扫描行数 | 1,000,010 | 10 |
| 磁盘 I/O | 高(全表扫描) | 低(索引 range) |
| 响应时间 | 秒级 | 毫秒级 |
| 扩展性 | O(n) | O(1) |
📊 实测数据(1 亿行表):
OFFSET 1000000, 10:12.3 秒- 游标分页:0.008 秒
五、终极心法
**“游标不是魔法,
而是排序的锚点——
- 当你 记录最大值,
你在跳过扫描;- 当你 复合排序,
你在确保连续;- 当你 接受最终一致,
你在拥抱现实。真正的分页优化,
始于对排序的敬畏,
成于对细节的精控。”
结语
从今天起:
- 深度分页必用游标方案
- 复合排序末尾加主键
- 用
EXPLAIN验证执行计划(type=range)
因为最好的分页,
不是跳过百万行,
而是精准定位下一程。
更多推荐


所有评论(0)