案例挖掘:千万数据表Limit 分页查询优化


点击上方“小罗技术笔记”,关注公众号

第一时间送达实用干货


作者:Tomo@An

来源:http://suo.im/6fuu5k


前言

在各类系统的表格类信息展示的功能中,经常会用到“翻页”这个操作,在页面上每次只展示有限的数据,需要看其他数据的时候则像翻书一样翻到后面的“页”。在 MySQL 支持的 SQL 语法中对此有特殊的支持,开发人员在实现这类功能的时候很方便:

  • select*fromxxx limit M,N

  • select*fromxxx limit N offset M

这两类语法代表的意思是一样的:返回从第 M 开始(不包括这一行)之后的 N 行数据。虽然使用起来很方便,但是这类语句存在查询性能上的陷阱,需要特别注意一下。

原理简介

在解释原理之前,先看一下实际的效果,看看这个“性能的陷阱”是什么。


两个语句的内容都非常简单,差别只在 limit 的部分,第一个语句跳过的行数很少,第二个语句跳过的行数很多,结果是两个语句的执行时间差了至少 200 倍。PS:limit 配合 order by 使用是一个好习惯,确保结果数据是稳定的

可以看到跳过的行数大幅度增长时,SQL 语句的执行时间也会快速增长,原因其实比较简单:在处理 limit M,N 的时候,MySQL 会先拿到 M+N 行结果数据,然后再丢弃 M 行数据,展示之后剩下的 N 行数据。所以上图的第二个语句实际上扫描了 800 多万行数据,然后丢弃了 800 万行数据,只展示之后的 1 行结果。

利用[慢查询分析三部曲][Link 1]的方法尝试排查一下,explain 和 optimizer_trace 都看不出来差别,但是 profile 里面能看出来两者的差距:

    

虽然都只输出一行结果,但是在 Sending data 阶段花费的时间差别很大,其实就是花在扫描 800 万行数据上去了。

优化策略

针对这个问题,其实有一个比较通用的优化思路:利用 join,先根据主键搜索到需要的数据,再通过主键关联到原来的表输出结果。SQL 可以改写一下:

SQL 改写的效果

可以看到查询时间降到了 1.5s 左右,提升了约 37%,看起来还可以,那么还有其他的办法么?

显然还是有的,不过这会要求表有自增主键。在分页查询的时候,记录上一次查询结果中的主键,然后在 where 条件中添加主键的范围约束。以上面的查询为例,上次分页查询时的主键是 8000001,那么下次分页的时候,where 条件中添加一个主键约束:id>8000001,再来看看查询效果:

添加条件之后的效果

可以发现利用主键来筛选掉上一次分页前的所有数据后再用 limit,查询基本是马上返回结果的。不过要特别注意,这种方法是根据主键的顺序先做了一次筛选,不一定会适用于所有的业务场景,理论上 UUID 类的主键也可以用,但是改造 SQL 前务必确保查询结果是符合预期的

总结一下

MySQL 由于本身查询优化器覆盖到的场景不够全,慢查询的原因也千奇百怪,各类业务 SQL 在上线前尽量多覆盖一些场景,确保业务功能安全发布。


长按二维码关注

点个在看再走呗!

本文分享自微信公众号 - 小罗技术笔记(javaCodeNote)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章