MySQL分页而不重复查询?


115

我想知道是否有一种方法可以从MySQL查询中获取结果数量,并同时限制结果。

分页的工作方式(据我了解),首先我要做的是

query = SELECT COUNT(*) FROM `table` WHERE `some_condition`

得到num_rows(query)之后,我得到了结果数。但是为了实际限制结果,我必须执行第二个查询,例如:

query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10

我的问题:是否无论是要检索要给出的结果总数,还是要限制单个查询中返回的结果?或更有效的方法。谢谢!


7
尽管在query2中不会有COUNT(*)
dlofrodloh,2016年

Answers:


66

不,这就是要分页的应用程序必须要做的事。它是可靠且防弹的,尽管它使查询两次。但是您可以将计数缓存几秒钟,这会很有帮助。

另一种方法是使用SQL_CALC_FOUND_ROWS子句,然后调用SELECT FOUND_ROWS()。除了必须FOUND_ROWS()随后进行调用之外,还有一个问题:MySQL中有一个bug,它会影响ORDER BY查询,使其在大表上比两个查询的幼稚方法慢得多。


2
但是,除非您在事务中执行两个查询,否则这还不能完全证明竞争条件。不过,这通常不是问题。
NickZoic,2009年

“可靠”是指SQL本身总是会返回您想要的结果,而“防弹丸”是指没有妨碍您使用什么SQL的MySQL错误。根据我提到的错误,不同于将SQL_CALC_FOUND_ROWS与ORDER BY和LIMIT一起使用。
staticsan

5
对于复杂的查询,使用SQL_CALC_FOUND_ROWS来获取同一查询中的计数几乎总是比做两个单独的查询要慢。这是因为这意味着无论限制如何,都将需要全部检索所有行,然后仅返回LIMIT子句中指定的行。另请参阅我的回复,其中包含链接。
thomasrutter 2011年

根据您需要这样做的原因,您可能还想考虑不检索总结果。实施自动分页方法已成为一种越来越普遍的做法。诸如Facebook,Twitter,Bing和Google之类的网站使用这种方法已有很长时间了。
Thomas B

68

我几乎从来没有做过两次查询。

只需返回比需要多的一行,在页面上仅显示10,如果显示的行多,则显示“下一步”按钮。

SELECT x, y, z FROM `table` WHERE `some_condition` LIMIT 0, 11
// iterate through and display 10 rows.

// if there were 11 rows, display a "Next" button.

您的查询应以最相关的顺序返回。通常,大多数人都不会在意412中的第236页。

当您进行Google搜索时,结果不在第一页上,您可能会转到第二页,而不是第9页。


42
实际上,如果我在Google查询的第一页上找不到它,通常我会跳到第九页。
菲尔(Phil)

3
@Phil我之前听说过,但是为什么要这么做?
TK123

5
有点晚了,但这是我的理由。某些搜索受搜索引擎优化的链接服务器场支配。因此,前几页是不同的服务器场,它们在争夺位置1,因此有用的结果可能仍与查询相关联,只是不在顶部。
Phil

4
COUNT是一个聚合函数。如何在一个查询中返回计数所有结果?无论LIMIT设置为什么,上面的查询将仅返回1行。如果添加GROUP BY,它将返回所有结果,但是COUNT将不准确
pixelfreak 2012年

2
这是办法建议的Percona的一个: percona.com/blog/2008/09/24/...
techdude

26

避免重复查询的另一种方法是,首先使用LIMIT子句获取当前页面的所有行,然后如果检索到最大行数,则仅执行第二次COUNT(*)查询。

在许多应用程序中,最可能的结果是所有结果都适合一页,并且必须进行分页是一种例外,而不是常规。在这些情况下,第一个查询将不会检索最大数量的结果。

例如,关于stackoverflow问题的答案很少会溅到第二页上。对答案的评论很少会超出显示全部内容的5个左右的限制。

因此,在这些应用程序中,您只需简单地先使用LIMIT进行查询,然后只要不达到该限制,就可以确切知道有多少行,而无需执行第二个COUNT(*)查询-这应该涵盖大多数情况。


1
@thomasrutter我有同样的方法,但是今天发现了一个缺陷。结果的最后一页将没有分页数据。也就是说,假设每个页面应有25个结果,最后一页可能不会有那么多结果,例如,它有7个结果...这意味着count(*)将永远不会运行,因此不会向其显示分页用户。
Duellsy'8

2
否-如果说有200条结果,则查询下25条结果,而您只返回7条,这表明结果的总数为207,因此您无需使用COUNT(*)进行其他查询因为您已经知道该说什么了。您拥有显示分页所需的所有信息。如果您在无法向用户显示分页方面遇到问题,那么您可能在其他地方存在错误。
thomasrutter

15

在大多数情况下,与在一个单独的查询中进行查询相比,在两个单独的查询中进行查询要快得多,而且资源占用更少,即使这似乎是违反直觉的。

如果使用SQL_CALC_FOUND_ROWS,则对于大型表,它会使查询慢得多,甚至比执行两个查询慢得多,第一个查询使用COUNT(*),第二个查询使用LIMIT。原因是SQL_CALC_FOUND_ROWS导致获取行之后而不是之前应用LIMIT子句,因此它会在应用限制之前为所有可能的结果获取整行。索引无法满足此要求,因为它实际上是在获取数据。

如果您采用两种查询方法,第一种仅获取COUNT(*)而不实际获取实际数据,则可以更快地满足此要求,因为它通常可以使用索引,而不必获取实际的行数据。每一行。然后,第二个查询只需要查看第一个$ offset + $ limit行,然后返回即可。

MySQL性能博客中的这篇文章进一步说明了这一点:

http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

有关优化分页的更多信息,请查看此文章该文章


2

我的答案可能晚了,但是您可以跳过第二个查询(有限制),而仅通过后端脚本过滤信息。例如,在PHP中,您可以执行以下操作:

if($queryResult > 0) {
   $counter = 0;
   foreach($queryResult AS $result) {
       if($counter >= $startAt AND $counter < $numOfRows) {
            //do what you want here
       }
   $counter++;
   }
}

但是,当然,当您要考虑成千上万条记录时,它很快就会变得效率低下。预先计算的计数可能是个好主意。

这是有关此主题的好书:http : //www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf


链接死了,我猜这是正确的:percona.com/files/presentations/ppc2009/…。无法编辑,因为不确定是否可以编辑。
hectorg87 2014年

1
query = SELECT col, col2, (SELECT COUNT(*) FROM `table`) AS total FROM `table` WHERE `some_condition` LIMIT 0, 10

16
该查询仅返回表中的记录总数;不是符合条件的记录数。
劳伦斯·巴尔桑蒂

1
总记录数是分页(@Lawrence)所需要的。
imme 2014年

哦,好吧,只需将where子句添加到内部查询中,您便会获得正确的“总计”以及分页结果(页面是通过limit子句选择的
Erenor Paz

子查询count(*)将需要相同的where子句,否则它将不会返回正确数量的结果
AKrush95 '19

1

对于希望在2020年找到答案的任何人。根据MySQL文档:

“ SQL_CALC_FOUND_ROWS查询修饰符和随附的FOUND_ROWS()函数自MySQL 8.0.17起已弃用,并将在以后的MySQL版本中删除。作为替代,考虑使用LIMIT执行查询,然后使用COUNT(*)执行第二个查询并且没有LIMIT来确定是否还有其他行。”

我想这解决了。

https://dev.mysql.com/doc/refman/8.0/zh-CN/information-functions.html#function_found-rows


0

您可以在子查询中重用大多数查询,并将其设置为标识符。例如,一个电影查询在运行时查找包含字母排序的电影,在我的网站上看起来像这样。

SELECT Movie.*, (
    SELECT Count(1) FROM Movie
        INNER JOIN MovieGenre 
        ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
    WHERE Title LIKE '%s%'
) AS Count FROM Movie 
    INNER JOIN MovieGenre 
    ON MovieGenre.MovieId = Movie.Id AND MovieGenre.GenreId = 11
WHERE Title LIKE '%s%' LIMIT 8;

请注意,我不是数据库专家,希望有人可以对其进行更好的优化。从SQL命令行界面直接运行它,它们在我的笔记本电脑上都需要0.02秒左右的时间。


-14
SELECT * 
FROM table 
WHERE some_condition 
ORDER BY RAND()
LIMIT 0, 10

3
这不能回答问题,按兰德下订单真是个坏主意。
Dan Walmsley
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.