7

假设我有一个表 t 并且表 t 有 15000 个条目

假设查询

SELECT * FROM t WHERE t.nid <1000

返回 1000 行

但后来我只想要前 10 行,所以我做了一个 LIMIT

SELECT * FROM t WHERE t.nid <1000 LIMIT 10

是否可以构造一个查询,其中除了使用上面的 LIMIT 子句返回 10 行信息之外,它还返回满足 WHERE 子句中设置的条件的行的总数,因此除了返回 10上面的行,它也返回 1000,因为总共有 1000 行满足 WHERE 子句......并且都在单个查询中返回

4

6 回答 6

9

首选解决方案

首先,found_rows() 函数不可移植(它是一个 MySQL 扩展)并且将被删除。正如用户@Zveddochka 指出的那样,它已经在 MySQL 8.0.17 中被弃用。

但更重要的是,事实证明,如果您使用正确的索引,那么运行两个查询实际上会更快。该SQL_CALC_FOUND_ROWS指令是通过产生额外恢复成本的“虚拟扫描”实现的。当查询没有被索引时,这个成本将与 a 相同COUNT(),因此运行两个查询将花费双倍 - 即,使用SQL_CALC_FOUND_ROWS将使事情运行速度提高 50%。

但是当查询正确索引时会发生什么?Percona 的人检查了它。事实证明,不仅COUNT()速度非常快,因为它只访问元数据和索引,而且查询SQL_CALC_FOUND_ROWS速度更快,因为它不会产生任何额外的成本;两个查询组合的成本低于增强的单个查询的成本

SQL_CALC_FOUND_ROWS 的结果如下:对于每个 b 值,执行非缓存需要 20-100 秒,预热后需要 2-5 秒。这种差异可以通过这个查询所需的 I/O 来解释——mysql 访问这个查询可以在没有 LIMIT 子句的情况下产生的所有 10k 行。

结果如下:第一次运行此查询需要 0.01-0.11 秒,所有连续运行需要 0.00-0.02 秒。

因此,正如我们所见,SELECT+COUNT 的总时间(0.00-0.15 秒)远小于原始查询的执行时间(2-100 秒)。让我们来看看解释...

那么该怎么办?

// Run two queries ensuring they satisfy exactly the same conditions

$field1 = "Field1, Field2, blah blah blah";
$field2 = "COUNT(*) AS rows";

$where  = "Field5 = 'X' AND Field6 = 'Y' AND blah blah";

$cntQuery = "SELECT {$field2} FROM {$joins} WHERE {$where}";
$rowQuery = "SELECT {$field1} FROM {$joins} WHERE {$where} LIMIT {$limit}";

现在第一个查询返回计数,第二个查询返回实际数据。

旧答案(仅对非索引表有用)

不要这样做。如果您发现答案的这一部分比上面的部分更适合您,这几乎肯定是一个信号,表明您的设置中的其他东西不是最佳的 - 很可能您没有正确使用索引,或者您需要更新您的MySQL 服务器,或运行数据库分析/优化以更新基数统计信息

你可以,但我认为这将是一个性能杀手。

您最好的选择是使用SQL_CALC_FOUND_ROWSMySQL 扩展并发出第二个查询以使用 FOUND_ROWS() 恢复全部行数。

 SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;
 SELECT FOUND_ROWS();

参见例如http://www.arraystudio.com/as-workshop/mysql-get-total-number-of-rows-when-using-limit.html

或者您可以简单地运行不带LIMIT子句的完整查询,并仅检索前十行。然后,您可以根据需要使用一个查询,并且还可以通过mysql_num_rows(). 这并不理想,但对于大多数查询来说也不是那么灾难性。

但是,如果您最后这样做,请非常小心地关闭查询并释放其资源:我发现检索不到完整的结果集并忘记释放 rs 句柄是“元数据锁定”的一个突出原因

于 2012-09-28T18:28:48.607 回答
4

你可以试试SQL_CALC_FOUND_ROWS,它可以在不再次运行语句的情况下获得总记录数。

SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;   -- get records
SELECT FOUND_ROWS();                -- get count

参考:http ://dev.mysql.com/doc/refman/5.0/en/information-functions.html

于 2012-09-28T18:27:22.837 回答
4

“是否可以构造一个查询,其中除了使用上面的 LIMIT 子句返回 10 行信息之外,它还返回满足 WHERE 子句中设置的条件的行的总数”

是的,可以通过使用窗口函数即COUNT(*) OVER()(MySQL 8.0+)在单个查询中执行这两项操作:

SELECT t.*, COUNT(*) OVER() AS cnt 
FROM t 
WHERE t.nid <1000 
LIMIT 10;  

db<>小提琴演示


边注:

LIMIT没有明确ORDER BY是不确定的。它可能会在多次运行之间返回不同的结果。

于 2021-08-01T10:19:07.410 回答
2

有很多事情需要讨论。

  • LIMIT没有 a 的AORDER BY有点不可预测,因此有点无意义。

  • 但是如果你添加一个ORDER BY,它可能需要找到所有的行,对它们进行排序,然后只提供你想要的 10 行。

  • 或者,ORDER BY可以由INDEX.

您的特定查询,如果变成 2 个查询(根据 8.0.17 之后的需要),将是

SELECT * FROM t WHERE t.nid < 1000 LIMIT 10;
SELECT COUNT(*) FROM t WHERE t.nid < 1000;

请注意,这些中的每一个都将从中受益INDEX(nid)。第一个将从索引的 BTree 中挑选 10 个项目,然后在数据的 BTree 中查找它们——每个项目只涉及 10 行。第二个将扫描 INDEX 直到它达到 1000,并且不接触数据 BTree。

如果您按照建议添加 ORDER BY,则第一个查询

SELECT * FROM t WHERE t.nid < 1000 ORDER BY t.nid LIMIT 10;

将与上述相同。但

SELECT * FROM t WHERE t.nid < 1000 ORDER BY t.abcd LIMIT 10;

将需要扫描很多行,而且速度很慢。并且可能使用临时表和文件排序。(检查EXPLAIN细节。) INDEX(nid, abcd)会有所帮助,但只有一点点。

还有其他变体,例如索引何时可以“覆盖”。

“一次查询”的目标是什么?

  • 速度?——如上所述,还有其他更相关的因素。

  • 一致性?-- 您可能需要一个事务来避免,例如,从第一个查询中获取 N 行并从COUNT.

     BEGIN;
     SELECT * ...
     SELECT COUNT(*) ...
     COMMIT;
    
  • 单命令?-- 考虑一个结合了 2 个语句的存储过程。或者

     SELECT * FROM t WHERE t.nid < 1000 LIMIT 10
     UNION ALL
     SELECT COUNT(*) FROM t WHERE t.nid < 1000;
    

但这变得很棘手,因为列数不同,因此需要一些杂凑才能使第二个查询具有相同的列数。另一个变体涉及GROUP BY WITH ROLLUP. (但它可能更难制造。)

卢卡斯的答案看起来很有希望。但是,它提供了一个额外的列(这可能很好),它的性能需要测试。如果您使用的是 8.0 并且他们的答案对您很有效,请接受该答案。

于 2021-08-02T00:57:24.630 回答
0

Count(*) 时间复杂度为 O(1),所以可以使用子查询

SELECT *, (SELECT COUNT(*) FROM t WHERE t.nid <1000) AS cnt
FROM t 
WHERE t.nid <1000 
LIMIT 10
于 2021-08-02T15:46:25.960 回答
-1

听起来你想要FOUND_ROWS()

SELECT SQL_CALC_FOUND_ROWS * FROM t WHERE t.nid <1000 LIMIT 10;
SELECT FOUND_ROWS();
于 2012-09-28T18:29:03.897 回答