2018/07 作者:ihunter 0 次 0
很多的朋友在面试中会遇到这样的问题,也有很多的项目在运营一段时间后也会遇到MYSQL查询中变慢的一些瓶颈,今天这儿简单的介绍下我常用的几种查询分页的方法,我所知道的也无非就是索引、分表、子查询偏移,所以要是有什么不对或有更好的方法,欢迎大家留言讨论。
效率分析关键词:explain + SQL语句
一,最常见MYSQL最基本的分页方式limit:
select * from `table` order by id desc limit 0, 20
在中小数据量的情况下,这样的SQL足够用了,唯一需要注意的问题就是确保使用了索引。随着数据量的增加,页数会越来越多,在数据慢慢增长的过程中,可能就会出现limit 10000,20这样的情况,limit 10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里,如果是limit 100000,100,需要扫描100100行,在一个高并发的应用里,每次查询需要扫描超过10W行,性能肯定大打折扣。
这种方式有几个不足:较大的偏移(OFFSET)会增加结果集,小比例的低效分页足够产生磁盘I/O瓶颈,需要扫描的行多。
简单的解决方法:不显示记录总数,没用户在乎这个数字;不让用户访问页数比较大的记录,重定向他们;避免count(*) ,不显示总数,让用户通过“下一页”来翻页 ,缓存总数;单独统计总数,在插入和删除时递增/递减
二,第二种就是分表,计算HASH值,这儿不做介绍了,我目前也没有在项目中真正使用过这种方法,还停留在理论层次;
三,第三种是偏移:
SELECT * FROM `table` WHERE id <= (SELECT id FROM `table` ORDER BY id desc LIMIT ".($page-1)*$pagesize.", 1) ORDER BY id desc LIMIT $pagesize
或者
select * FROM `table` AS t1 JOIN (SELECT id FROM `table` ORDER BY id desc LIMIT 900,1) AS t2 WHERE t1.id<=t2.id order by t1.id desc limit 5
原理就是记录住当前页id的最大值和最小值,计算跳转页面和当前页相对偏移,由于页面相近,这个偏移量不会很大,这样的话m值相对较小,大大减少扫描的行数。其实传统的limit m,n,相对的偏移一直是第一页,这样的话越翻到后面,效率越差,而上面给出的方法就没有这样的问题。
比如还是SELECT * FROM `table` ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是9527,最小的是9500,如果我们只提供”上一页”、”下一页”这样的跳转(不提供到第N页的跳转),那么在处理”上一页”的时候SQL语句可以是:
SELECT * FROM `table` WHERE id > 9527 ORDER BY id ASC LIMIT 20;
处理”下一页”的时候SQL语句可以是:
SELECT * FROM `table` WHERE id < 9500 ORDER BY id DESC LIMIT 20;
不管翻多少页,每次查询只扫描20行。
缺点是只能提供”上一页”、”下一页”的链接形式,但是我一般来说非常喜欢”<上一页 1 2 3 4 5 6 7 8 9 下一页>”这样的链接方式,怎么办呢?
如果LIMIT m,n不可避免的话,要优化效率,只有尽可能的让m小一下,我们扩展前面做法,还是SELECT * FROM `table` ORDER BY id DESC,按id降序分页,每页20条,当前是第10页,当前页条目id最大的是9527,最小的是9500,比如要跳到第8页,我看的SQL语句可以这样写:
SELECT * FROM `table` WHERE id > 9527 ORDER BY id ASC LIMIT 20,20;
跳转到第13页:
SELECT * FROM `table` WHERE id > 9527 ORDER BY id ASC LIMIT 20,20;
注意SQL语句里面的ASC和DESC,如果是ASC取出来的结果,显示的时候记得倒置一下。
整体来说在面对百万级数据的时候如果使用上面第三种方法来优化,系统性能上是能够得到很好的提升,在遇到复杂的查询时也尽量简化,减少运算量。 同时也尽量多的使用内存缓存,有条件的可以考虑分表、分库、阵列之类的大型解决方案了。