MySQL分页查询优化

以前搬砖的时候遇到对行数大的表进行分页的操做,性能好差。最近在读《高性能MySQL》,正好讲到这个方面的,记录一下(基本上都是原文)。mysql

优化LIMIT分页

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

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

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

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

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

mysql> 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经过范围扫描得到到对应的结果。例如,若是在一个位置列上有索引,而且预先计算出了边界值,上面的查询就能够改写为:code

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

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

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

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

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

优化SQL_CALC_FOUND_ROWS

分页的时候,另外一个经常使用的技巧是在LIMIT语句中加上SQL_CALC_FOUND_ROWS提示(hint),这样作能够得到去掉LIMIT之后知足条件的行数,所以能够做为分页的总数。看起来,MySQL作了一些很是高深的优化,像是经过某种方法预测了总行数。但实际上MySQL只有在扫描了全部知足条件的行,而后再抛弃掉不须要的行,而不是在知足LIMIT的行数后就终止扫描。全部该提示的代价可能很是高。(几年前本菜鸟在大学作项目的时候,就是用这个查询优化器的提示,觉得这样会减小查询次数和扫描行数,后来我在工做后操做几百万行数的表的时候,这种方法性能不好)

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

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

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