首页 > 代码库 > 优化LIMIT分页

优化LIMIT分页

优化LIMIT分页

在系统中需要分页的操作通常会使用limit加上偏移量的方法实现,同时加上合适的order by 子句。如果有对应的索引,通常效率会不错,否则MySQL需要做大量的文件排序操作。

一个非常令人头疼问题就是当偏移量非常大的时候,例如可能是limit 10000,20这样的查询,这是mysql需要查询10020条然后只返回最后20条,前面的10000条记录都将被舍弃,这样的代价很高。如果所有的页面被访问的频率相同,那么这样的查询平均需要访问半个表的数据。要优化这样的查询,要么实在页面中限制分页的数量,要么是优化大偏移量的性能。

优化此类查询的一个最简单的方法是尽可能的使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作再返回所需的列。对于偏移量很大的时候这样做的效率会得到很大提升。对于下面的查询:

Select id, description from news order by title limit 50,5;

如果这个表非常大,那么这个查询可以改写成如下的方式:

Select news.id, news.description from news inner join (select id from news order by title limit 50,5) as myNew using(id);

这里的“延迟关联”将大大提升查询的效率,它让MySQL扫描尽可能少的页面,获取需要的记录后再根据关联列回原表查询需要的所有列。这个技术也可以用在优化关联查询中的limit。

Limit和offset的问题其实是offset的问题,它会导致MySQL扫描大量不需要的行然后再抛弃。如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该位置开始扫描,这样可以避免使用offset。例如:

Select id, description from news where id >10000 limit 5;这样做的好处是无论翻页到多么后面,其性能都会很好。

其他优化的办法还包括使用预先计算的汇总表,或者关联一个冗余表,冗余表只需要包含主键列和需要排序的数据列。还可以使用Sphinx优化一些搜索操作。

还有一种做法就是做数据缓存。先获得并缓存较多的数据,例如,缓存一千条,然后每次分页都从这个缓存中获得。

 

关于分页的总条数:

分页的时候另一个常用的技巧是在LIMIT语句中加上SQL_CALL_FOUND_ROWS提示(即hint),这样就可以获得去掉limit以后满足条件的行数,因此可以作为分页的总行数。但实际上MySQL只有在扫描了满足条件的所有行后才知道总行数。所以加上这个提示之后不管需不需要都会扫描所有的行,然后再抛弃掉不需要的行,而不是在满足limit的行数后就终止扫描。所以该提示的代价可能非常高。

有时候也可以考虑EXPLAIN的结果中的rows列的值作为结果集总数的近似值(实际上Google的搜索结果也是个近似值)。当需要精确的结果的时候再单独使用COUNT(*)来满足需求。

不过可以查询一次总行数并缓存起来,这样就不用每次都查询一下总行数了。

 

优化LIMIT分页