P71 MyBatis 查 100 万页还在用 Limit Offset?回去等通知吧
面试题:数据量很大(百万级)时,分页还在用 LIMIT offset, size,有什么问题?怎么优化?
1. 问题
sql
SELECT * FROM t ORDER BY id LIMIT 1000000, 20;MySQL 会扫描并丢弃前 100 万行,offset 越大越慢(详见 P32 深分页)。在 MyBatis 里用 PageHelper 传 pageNum 也是同样原理——翻页越深,SQL 越慢。
2. MyBatis 场景下的优化
① 游标式分页(推荐)
不用 pageNum,用上一页的最后一条记录:
xml
<select id="pageByCursor" resultType="Order">
SELECT * FROM t_order
WHERE id > #{lastId}
ORDER BY id
LIMIT #{size}
</select>java
List<Order> page = orderMapper.pageByCursor(lastId, 20);每次只扫 20 行,翻 100 万页也不慢。
② 延迟关联
sql
SELECT o.* FROM t_order o
JOIN (SELECT id FROM t_order ORDER BY id LIMIT 1000000, 20) tmp
ON o.id = tmp.id;子查询只走主键覆盖索引,再回表取 20 行。
③ 覆盖索引
列表页只查必要字段,建立覆盖索引((status, create_time, id)),避免回表。
④ 限制深度
- 业务上禁止跳页翻到很深的页码,用"加载更多";
- PageHelper 可设置
reasonable参数防越界,但治标不治本。
3. PageHelper 的注意点
- PageHelper 的 count 查询在大表上也很慢(可以关掉 count);
- 分页 SQL 会包一层 count + limit,offset 深一样慢;
- 动态 SQL/多表查询时 PageHelper 可能拦截错 SQL。
4. 加分点
- 说明"深分页慢的本质是 OFFSET 扫描丢弃";
- 提到配合 ES 等搜索引擎做海量数据搜索分页(from+size 也有深度限制,用 search_after);
- 结合业务:管理后台的"翻到第 10 万页"没有意义,产品上就该限制。
一句话总结
深分页 LIMIT offset,size 会扫描并丢弃海量行;MyBatis 场景用游标式(WHERE id > lastId)+ 延迟关联/覆盖索引替代 PageHelper 的深翻页,并配合业务限制翻页深度。