从相关渠道了解到,为什么慢? MySQL 需要先扫描 1000010 条记录,然后丢弃前 1000000 条,只返回最后 10 条。 科技新闻。
另外,有些时候技术手段不够,还可以从产品层面软优化:
这是一种代码可复用、防SQL注入的写法,技术亮点在于用 WHERE > + 动态排序参数处理。
对千背景与起因
“好的。普通 LIMIT offset, size 在浅分页时没问题,但一旦 offset 达到百万级,就会变成灾难。比如:
“没问题,我分别给出游标分页和延迟关联的关键代码,然后拆解难点。”
这样就能覆盖绝大多数场景了。” 😎
对千事件经过
Java 代码里把上一页最后一个记录的 createTime 和 id 都传过来即可,完美保证游标准确性 🎯。
“咱们来道场景题。假设有一张千万级别的用户订单表,需要做分页查询,你会怎么设计和优化?先说说普通分页有什么坑?”
执行过程可以这样理解:
对千各方回应
“除了这两种,还有没有更极致的优化思路?”
很多同学没注意:只按 create_time 排序,若很多行岁月相同,MySQL 在两次查询间可能返回重复记录或漏掉数据。解决办法很简单:
“但如果产品要求必须能跳页,比如管理后台要翻到第 200 页,游标分页就不好使了,你怎么做?”
对千影响分析
这些在工程上非常实用。”
适用场景:需要按非主键字段排序的场景
原理:先在索引中定位偏移量,再回表查询数据
A:不会漏数据,因为是基于 ID 的范围查询;但会导致每页数量不一致,可通过业务层补偿。
技术亮点:利用索引覆盖定位偏移量,减少 99% 的回表次数
所以优化的核心就是:尽量缩减扫描行数 + 避免无效回表。
数据库需要先扫描并丢弃前 100 万行,才能拿到最后的 20 行,这个过程浪费大量 IO 和 CPU,RT 可能飙到秒级。底层逻辑大概是这样:
技术亮点:泛型通用设计、函数式编程解耦、毫秒级稳定性能
原理:先通过覆盖索引获取需要的 ID 列表,再通过 ID 关联查询完整数据
“这种场景我会用子查询 + 延迟关联。核心思想是:先让子查询只走覆盖索引拿到主键,避免回表,再用主键关联原表取完整字段。
技术亮点:先通过覆盖索引获取 ID 列表,再小结果集关联查询
“那你优先会怎么解决?”
A:建立联合索引,游标条件包含所有排序字段;或使用覆盖索引 + 回表方案。
这样每次只扫描 20 行,性能几乎不受 offset 影响,非常稳。我做过简单压测对比:
“如果查询的字段恰好全都在一个索引里,可以直接用覆盖索引分页,连回表都省了。例如只需要 id, create_time, user_name,并且索引覆盖了这些列,那即使是大 offset 也快很多。这需要和业务方一起抠字段,能少查绝不多查。
“嗯,那如果数据量再大,或带复杂筛选,你会考虑其他存储吗?”
原理:利用主键索引的有序性,记录上一页最后一条的 ID,直接定位下一页
“你刚才讲的方案都很到位,那能不能写一下核心代码,让我看看实际落地你怎么写?另外,你觉得这整个深分页场景的技术难点有哪些,你是怎么解决的?”
A:当偏移量超过 10 万时,传统分页就会出现明显性能问题;千万级表偏移量到 100 万时,基本会超时。
“最推荐的方案是游标分页,也就是 Keyset Pagination。思路是:每次记住上一页最后一条记录的主键(或排序字段),下一页直接从这个位置往后取。
前提是必须建有 (create_time, id) 这类覆盖索引。虽然仍要扫描 100 万行索引,但比起扫描数据行要轻量得多,最后只回表 20 次,性能会有数量级提升。”
不过它也有明显缺点:不支持任意跳页,只适合「加载更多」或瀑布流场景。
“会。单表千万级 MySQL 还能扛,但如果加上复杂全文检索、多维筛选与排序的分页,我会建议将数据同步到 Elasticsearch,用 search_after 或 scroll 实现深度分页。不过这就属于架构演进,单表分页的核心解法还是上面那几种。总结一下:
“不错,代码和难点梳理得很扎实,尤其排序唯一性情况能注意到,说明你确实在工程里踩过坑。那我没什么可问的了,咱们HR环节见!”