MySQL的LIMIT与分页优化

  1. select * from table LIMIT 5,10; #返回第6-15行数据   mysql

  2. select * from table LIMIT 5; #返回前5行   sql

  3. select * from table LIMIT 0,5; #返回前5行  数据库


性能优化:浏览器

[sql] view plaincopyprint?在CODE上查看代码片派生到个人代码片缓存

  1. 基于MySQL5.0中limit的高性能,我对数据分页也从新有了新的认识.  性能优化

  2.   

  3. 1.  架构

  4. Select * From cyclopedia Where ID>=(  性能

  5. Select Max(ID) From (  大数据

  6.  Select ID From cyclopedia Order By ID limit 90001  优化

  7. As tmp  

  8. ) limit 100;  

  9.   

  10. 2.  

  11. Select * From cyclopedia Where ID>=(  

  12. Select Max(ID) From (  

  13.  Select ID From cyclopedia Order By ID limit 90000,1  

  14. As tmp  

  15. ) limit 100;  

  16.   

  17. 一样是取90000条后100条记录,第1句快仍是第2句快?  

  18. 第1句是先取了前90001条记录,取其中最大一个ID值做为起始标识,而后利用它能够快速定位下100条记录  

  19. 第2句择是仅仅取90000条记录后1条,而后取ID值做起始标识定位下100条记录  

  20. 第1句执行结果.100 rows in set (0.23) sec  

  21. 第2句执行结果.100 rows in set (0.19) sec  

  22.   

  23. 很明显第2句胜出.看来limit好像并不彻底像我以前想象的那样作全表扫描返回limit offset+length条记录,这样看来limit比起MS-SQL的Top性能仍是要提升很多的.  

  24.   

  25. 其实第2句彻底能够简化成  

  26.   

  27. Select * From cyclopedia Where ID>=(  

  28. Select ID From cyclopedia limit 90000,1  

  29. )limit 100;  

  30.   

  31. 直接利用第90000条记录的ID,不用通过Max运算,这样作理论上效率因该高一些,但在实际使用中几乎看不到效果,由于自己定位ID返回的就是1条记录,Max几乎不用运做就能获得结果,但这样写更清淅明朗,省去了画蛇那一足.  

  32.   

  33. 但是,既然MySQL有limit能够直接控制取出记录的位置,为何不干脆用Select * From cyclopedia limit 90000,1呢?岂不更简洁?  

  34. 这样想就错了,试了就知道,结果是:1 row in set (8.88) sec,怎么样,够吓人的吧,让我想起了昨天在4.1中比这还有过之的"高分".Select * 最好不要随便用,要本着用什么,选什么的原则, Select的字段越多,字段数据量越大,速度就越慢. 上面2种分页方式哪一种都比单写这1句强多了,虽然看起来好像查询的次数更多一些,但其实是以较小的代价换取了高效的性能,是很是值得的.  


LIMIT偏移量越大,从磁盘IO读取的记录行数就越多,因此要尽量少的从磁盘IO读取数据,总的来讲有如下几种方式:

1.子查询优化法  
先找出第一条数据,而后大于等于这条数据的id就是要获取的数据  
缺点:数据必须是连续的,能够说不能有where条件,where条件会筛选数据,致使数据失去连续性

2.倒排表优化法  
倒排表法相似创建索引,用一张表来维护页数,而后经过高效的链接获得数据  
缺点:只适合数据数固定的状况,数据不能删除,维护页表困难 

3.反向查找优化法  
当偏移超过一半记录数的时候,先用排序,这样偏移就反转了  
缺点:order by优化比较麻烦,要增长索引,索引影响数据的修改效率,而且要知道总记录数,偏移大于数据的一半 

4.limit限制优化法  
把limit偏移量限制低于某个数。。超过这个数等于没数据,我记得alibaba的dba说过他们是这样作的 

总结:limit的优化限制都比较多,因此实际状况用或者不用只能具体状况具体分析了。页数那么后,基本不多人看的。。。 


====================================================================================================================================

分页优化的四种方式

好久之前读了一篇关于分页的文章,后来越想越有道理,最近又从新找出来,并作了翻译,原文参考:Four ways to optimize paginated displays.

翻译背景:在大数据量的状况下,本来很简单的分页若是没有处理好,你会发现分页的请求会消耗你大量的数据库时间。若是你遇到了这个问题,文章给了你几个很好的解决的方案。固然,初学者若能看完这篇文章,那么它会指导你写出更具备扩展性的分页代码。

全文概述:文中提到了分页的办法总结以下:

  1. 所有缓存查询结果。把查询结果所有缓存起来(例如文件缓存、静态化结果页面等)。

  2. 不详细显示总共有多少分页。这里有两个优化的技巧。其一每次在计算总条目的时候,我就固定查询501条,而后将前500条分页显示好,若是第501条确实存在,那么给出按钮 “查看更多...”(这种状况会不多)。其二,在每次列表本页面的时候,好比第一页我要显示1-20条,那么我查询出1-21条。若是第21条真的存在,我就给出"下一页"按钮,依次类推。

    事实上google就是这样作的。在查看第一页搜索结果的时候google只会显示前十页(共100个条目),并不显示搜索结果条目总共有多少:
    首页的分页显示
    查看第二页的时候,仅仅会多显示一页
    第二页的分页显示

  3. 经过EXPLAIN的"row"列来估算结果总共有多少条目。文章中称google是这样估算结果集的:google总结果集


全文译文:

在实际开发中,分页显示是咱们最常遇到的优化问题之一。例如搜索结果、积分列表、排行榜等。分页的通常模型:在一个排序的结果集合(较大)中咱们要显示其中连续20条目;而且须要显示 “下一页”、”上一页”的连接;有时候咱们还须要显示,总共有多少个条目,一共分了多少页。

要给出这样一个完成显示,数据库的代价是很大的,有时候就为了显示这么一个分页,须要执行的SQL会比整个页面显示其余的所有SQL消耗还要大。
我曾遇到这样的案例:有一次在为咱们的一个客户作Slow Query LOG分析的时候咱们就发现:整个LOG 里面的SQL耗时6300s,其中两个主要的分页查询大约消耗了(2850 + 380)秒,占了整个Slow Query的50%。
分页没有处理好就是这么糟糕~.

咱们来分析一下分页的通常状况:

#典型分页的SQL以下:
SELECT .... FROM ... ORDER BY .... LIMIT X, 20

若是ORDER BY部分没有可以用索引的话(这样的状况仍是不少的),MYSQL就会作文件排序(filesort);假想若是若是知足WHERE 条件的条目共有个百万的数量级的话,那么MYSQL就会取出这上百万的结果,临时存储、文件排序,而后再删除大大部分的数据保留其中的20个。当用户点击“下一页”的时候,上面的过程会彻底重作一遍,只是取得结果向后偏了一点。要是你还想显示“总共有多少条目,共分多少页面”的话,通常是这样作(1)使用SQL_CALC_FOUND_ROWS (2)执行一个单独的SQL去计算行数。若是用户的每一次请求都执行以上的操做,能够想象当你的数据量愈来愈大的时候,状况会愈来愈糟。

事实上,有不少办法去优化上面的过程的。(关于这一点我以前我写过的一篇article on optimizing ranked data 。不过那篇文章里面介绍的办法实施起来比较困难。因此若是不是状况复杂和重要到必定程度,就不值得那样作。)那通常状况怎么办呢?除了索引、重组数据、SQL优化,咱们还有两个大的方面能够考虑去作。其一,积极的把SQL的查询结果缓存起来,从而减小SQL执行;其二就是从新考虑一下你的分页就架构,在应用中,并非每次都须要把分页的各个部分都完整显示出来的。例如你把从第1到50页的连接都给出来,不少时候用户根本不会直接去点击某一页。咱们考虑的思路是指把最重要的部分先展现出来。

这样考虑的因而就有了下面四个优化的建议来提升性能

  1. 首次查询的时候缓存结果。这样状况就变得简单了,不管是结果条目的数量,总共的页面数量,仍是取出其中的部分条目。

  2. 不显示总共有多少条目。Google搜索结果的分页显示就用了这个特性。不少时候你可能看了前几页,就够了。那么我能够这样,每次我都把结果限制在500条(这个数据越大 资源消耗越大)而后你每次查询的时候,都查询501条记录,这样,若是结果然有501个,那么咱们就显示连接 “显示下500条记录”。

  3. 不显示总页面数。只给出“下一页”的连接,若是有下一页的话。(若是用户想看上一页的话,他会经过浏览器来回到上一页的)。那你可能会问我“不显示总页面数”怎么知道是否是有下一页呢?这里有一个很好的小技巧:你在每次显示你当前页面条目的时候你都多查询一条,例如你要显示第11-20个条目时,你就取出11-21条记录(多取一条,并不显示这多取的内容),那么当你发现第21条存在的时候就显示“下一页的连接”,不然就是末页了。这样你就不用每次计算总页面数量了,特别是在作缓存很困难的时候这样作效率很是好。

  4. 估算总结果数。Google就是这么作的,事实证实效果很好。用EXPLAIN 来解释你的SQL,而后经过EXPLAIN的结果来估算。EXPLAIN结果有一列”row”会给你一个大概的结果。(这个办法不是到处都行,可是某些地方效果是很好的)这些办法能够很大程度上减轻数据库的压力,并且对用户体验不会有什么影响。

这些办法能够很大程度上减轻数据库的压力,并且对用户体验不会有什么影响。




在系统中须要进行分页操做的时候,咱们一般会使用LIMIT加上偏移量的办法实现,同时加上合适的ORDER BY子句。若是有对应的索引,一般效率会不错,不然,MySQL须要作大量的文件排序操做。

一个很是常见又使人头疼的问题就是,在偏移量很是大的时候,例如多是LIMIT 10000,20这样的查询,这时MySQL须要查询10020条记录后只返回最后20条,前面10000条记录都将被抛弃,这样的代价很是高。若是全部的页面被访问的频率都相同,那么这样的查询平均须要访问半个表的数据。要优化这种查询,要么是在页面中限制分页的数量,要么是优化大偏移量的性能。

优化此类分页查询的一个最简单的办法就是尽量地使用索引覆盖扫描,而不是查询全部的列。而后根据须要作一次关联操做再返回所需的列。对于偏移量很大的时候,这样的效率会提高很是大。考虑下面的查询:

    SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;

若是这个表很是大,那么这个查询最好改写成下面的这样子:

    SELECT film.film_id, film.description FROM sakila.film 
    INNER JOIN (
      SELECT film_id FROM sakila.film ORDER BY title LIMIT 50,5
    ) AS lim USING(film_id);

这里的“延迟关联”将大大提高查询效率,它让MySQL扫描尽量少的页面,获取须要访问的记录后再根据关联列回原表查询须要的全部列。这个技术也能够用于优化关联查询中的LIMIT子句。

有时候也能够将LIMIT查询转换为已知的位置的查询,让MySQL经过范围扫描得到到对应的结果。例如,若是在一个位置列上有索引,而且预先计算出了边界值,上面的查询就能够改写为:

    SELECT film_id, description FROM sakila.film 
    WHERE position BETWEEN 50 AND 54 ORDER BY position;

对数据进行排名的问题也与此相似,但每每还会同时和GROUP BY混合使用。在这种状况下一般都须要预先计算并存储排名信息。

LIMIT和OFFSET的问题,实际上是OFFSET的问题,它会致使MySQL扫描大量不须要的行而后再抛弃掉。若是可使用书签记录上次取数据的位置,那么下次就能够直接从该书签记录的位置开始扫描,这样就能够避免使用OFFSET。例如,若须要按照租借记录作翻页,那么能够根据最新一条租借记录向后追溯,这种作法可行是由于租借记录的主键是单调增加的。首先使用下面的查询得到一组结果:

    SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;

假设上面的查询返回的是主键为16049到16030的租借记录,那么下一页查询就能够从16030这个点开始:

    SELECT * FROM sakila.rental WHERE rental_id < 16030
    ORDER BY rental_id DESC LIMIT 20;

该技术的好处是不管翻页到那么后面,其性能都会很好。

其余优化办法还包括使用预先计算的汇总表,或者关联到一个冗余表,冗余表只包含主键列和须要作排序的数据列。

分页的时候,另外一个经常使用的技巧是在LIMIT语句中加上SQL_CALC_FOUNT_ROWS提示(hint),这样就能够得到去掉LIMIT之后知足条件的行数,所以能够做为分页的总数。看起来,MySQL作了一些很是“高深”的优化,像是经过某种方法预测了总行数。但实际上,MySQL只有在扫描了全部知足条件的行之后,才会知道行数,因此加上这个提示之后,无论是否须要,MySQL都会扫描全部知足条件的行,而后再抛弃掉不须要的行,而不是在知足LIMIT的行数后就终止扫描。因此该提示的代价可能很是高。

一个更好的设计是将具体的页数换成“下一页”按钮,假设每页显示20条记录,那么咱们每次查询的时候都是LIMIT返回21条记录并只显示20条,若是第21条存在,那么咱们就显示“下一页”按钮,不然就说明没有更多的数据,也就无须显示“下一页”按钮了。

另外一种作法是先获取并缓存较多的数据,例如,缓存1000条,而后每次分页都从这个缓存中获取。这样作可让应用程序根据结果集的大小采起不一样的策略,若是结果集少于1000,就能够在页面上显示全部的分页连接,由于数据都在缓存中,因此这样作性能不会有问题。若是结果集大于1000,则能够在页面上设计一个额外的“找到的结果多于1000条”之类的按钮。这两种策略都比每次生成所有结果集再抛弃掉不须要的数据的效率要高不少。

有时候也能够考虑使用EXPLAIN的结果中的rows列的值来做为结果集总数的近似值(实际上Google的搜索结果总数也是个近似值)。当须要精确结果的时候,再单独使用COUNT(*)来知足需求,这时若是可以使用索引覆盖扫描则一般也会比SQL_CALC_FOUND_ROWS快得多。

相关文章
相关标签/搜索