没有双重查询的MySQL分页?
Posted
技术标签:
【中文标题】没有双重查询的MySQL分页?【英文标题】:MySQL pagination without double-querying? 【发布时间】:2010-10-23 13:05:00 【问题描述】:我想知道是否有办法从 mysql 查询中获取结果数量,同时限制结果。
分页的工作方式(据我所知),首先我会做类似的事情
query = SELECT COUNT(*) FROM `table` WHERE `some_condition`
得到 num_rows(query) 后,我就有了结果的数量。但是为了真正限制我的结果,我必须进行第二个查询,例如:
query2 = SELECT COUNT(*) FROM `table` WHERE `some_condition` LIMIT 0, 10
我的问题:是否既要检索将给出的结果总数,又要限制在单个查询中返回的结果?或者任何更有效的方式来做到这一点。谢谢!
【问题讨论】:
虽然你不会在 query2 中有 COUNT(*) 【参考方案1】:我几乎从不做两次查询。
只需返回比需要的多行,页面上只显示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 查询的第一页找不到它,我通常会跳到第九页。 @Phil 我以前听说过,但为什么要这样做? 有点晚了,但这是我的推理。一些搜索由搜索引擎优化的链接农场主导。所以前几页是不同的农场争夺第一名,有用的结果可能仍然与查询相关,只是不在顶部。COUNT
是一个聚合函数。如何在一个查询中返回计数和所有结果?上面的查询只会返回 1 行,无论 LIMIT
设置在什么位置。如果添加GROUP BY
,它将返回所有结果,但COUNT
将不准确
这是 Percona 推荐的方法之一:percona.com/blog/2008/09/24/…【参考方案2】:
不,有多少想要分页的应用程序必须这样做。它可靠且防弹,尽管它会进行两次查询。但是您可以将计数缓存几秒钟,这将有很大帮助。
另一种方法是使用SQL_CALC_FOUND_ROWS
子句,然后调用SELECT FOUND_ROWS()
。除了您必须在之后调用FOUND_ROWS()
之外,还有一个问题:a bug in MySQL 会影响ORDER BY
查询,这使得它在大型表上比两个查询的幼稚方法慢得多.
【讨论】:
然而,除非您在事务中执行两个查询,否则它并不是完全的竞争条件证明。不过,这通常不是问题。 “可靠”我的意思是 SQL 本身总是会返回你想要的结果,而“防弹”我的意思是没有 MySQL 错误阻碍你可以使用的 SQL。根据我提到的错误,与使用带有 ORDER BY 和 LIMIT 的 SQL_CALC_FOUND_ROWS 不同。 在复杂查询中,使用 SQL_CALC_FOUND_ROWS 在同一个查询中获取计数几乎总是比执行两个单独的查询要慢。这是因为这意味着无论限制如何,都需要完整检索所有行,然后只返回 LIMIT 子句中指定的行。另请参阅我的回复,其中包含链接。 根据您需要它的原因,您可能还想考虑不检索总结果。实现自动分页方法已成为一种更常见的做法。 Facebook、Twitter、Bing 和 Google 等网站多年来一直在使用这种方法。【参考方案3】:另一种避免双重查询的方法是首先使用 LIMIT 子句获取当前页面的所有行,然后如果检索到最大行数,则仅执行第二次 COUNT(*) 查询。
在许多应用程序中,最可能的结果是所有结果都适合一页,并且必须进行分页是例外而不是常态。在这些情况下,第一个查询不会检索到最大数量的结果。
例如,*** 问题的答案很少会溢出到第二页。对答案的评论很少会超出显示全部所需的 5 条左右的限制。
因此,在这些应用程序中,您可以简单地首先使用 LIMIT 进行查询,然后只要未达到该限制,您就可以准确地知道有多少行,而无需进行第二次 COUNT(*) 查询- 应该涵盖大多数情况。
【讨论】:
@thomasrutter 我有同样的方法,但是今天发现了一个缺陷。结果的最后一页将没有分页数据。即,假设每个页面应该有 25 个结果,最后一页可能没有那么多,假设它有 7 个......这意味着 count(*) 将永远不会运行,因此不会向用户。 否 - 如果你说,有 200 个结果,你查询接下来的 25 个,你只得到 7 个返回,这告诉你结果总数是 207,因此你不需要用 COUNT(*) 做另一个查询,因为你已经知道它会说什么。您拥有显示分页所需的所有信息。如果您遇到分页没有向用户显示的问题,那么您在其他地方有错误。【参考方案4】:在大多数情况下,在两个单独的查询中执行此操作比在一个查询中执行要快得多且资源消耗更少,尽管这似乎违反直觉。
如果您使用 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/
有关优化分页的更多信息,请查看this post 和this post。
【讨论】:
【参考方案5】:对于任何在 2020 年寻找答案的人。根据 MySQL 文档:
“SQL_CALC_FOUND_ROWS 查询修饰符和随附的 FOUND_ROWS() 函数自 MySQL 8.0.17 起已弃用,并将在未来的 MySQL 版本中删除。 作为替代,考虑使用 LIMIT 执行查询,然后使用 COUNT(*) 且没有 LIMIT 的第二个查询来确定是否还有其他行。"
我想这就解决了。
https://dev.mysql.com/doc/refman/8.0/en/information-functions.html#function_found-rows
【讨论】:
【参考方案6】:我的回答可能会迟到,但您可以跳过第二个查询(有限制),只需通过后端脚本过滤信息。例如,在 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/…。不会编辑,因为不确定是否是。【参考方案7】:query = SELECT col, col2, (SELECT COUNT(*) FROM `table`)/10 AS total FROM `table` WHERE `some_condition` LIMIT 0, 10
其中10是页面大小,0是页码(需要在查询中使用pageNumber-1)
【讨论】:
这个查询只返回表中的记录总数;不是符合条件的记录数。 记录总数是分页所需的(@Lawrence)。 哦,好吧,只需将where
子句添加到内部查询中,您就会得到正确的“总数”以及分页结果(使用limit
子句选择页面
子查询 count(*) 需要相同的 where 子句,否则它不会返回正确数量的结果【参考方案8】:
您可以在子查询中重用大部分查询并将其设置为标识符。例如,查找包含按运行时排序的字母“s”的电影的电影查询在我的网站上看起来像这样。
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 秒。
【讨论】:
【参考方案9】:SELECT *
FROM table
WHERE some_condition
ORDER BY RAND()
LIMIT 0, 10
【讨论】:
这并不能回答问题,按 rand 订购是一个非常糟糕的主意。以上是关于没有双重查询的MySQL分页?的主要内容,如果未能解决你的问题,请参考以下文章