Welches ist am schnellsten? SELECT SQL_CALC_FOUND_ROWS FROM `table` oder SELECT COUNT (*)

176

Wenn Sie die Anzahl der Zeilen begrenzen, die von einer SQL-Abfrage zurückgegeben werden sollen, die normalerweise beim Paging verwendet wird, gibt es zwei Methoden, um die Gesamtzahl der Datensätze zu bestimmen:

Methode 1

Fügen Sie die SQL_CALC_FOUND_ROWSOption in das Original ein SELECTund ermitteln Sie die Gesamtzahl der Zeilen, indem Sie Folgendes ausführen SELECT FOUND_ROWS():

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

Methode 2

Führen Sie die Abfrage normal aus, und ermitteln Sie dann die Gesamtzahl der Zeilen, indem Sie sie ausführen SELECT COUNT(*)

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

Welche Methode ist die beste / schnellste?

Jrgns
quelle

Antworten:

120

Es hängt davon ab, ob. Weitere Informationen finden Sie im MySQL Performance Blog-Beitrag zu diesem Thema: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Nur eine kurze Zusammenfassung: Peter sagt, dass es von Ihren Indizes und anderen Faktoren abhängt. Viele der Kommentare zu diesem Beitrag scheinen zu sagen, dass SQL_CALC_FOUND_ROWS fast immer langsamer ist - manchmal bis zu 10x langsamer - als das Ausführen von zwei Abfragen.

Nathan
quelle
27
Ich kann dies bestätigen. Ich habe gerade eine Abfrage mit 4 Verknüpfungen in einer 168.000-Zeilen-Datenbank aktualisiert. Die Auswahl nur der ersten 100 Zeilen mit einem SQL_CALC_FOUND_ROWSdauerte über 20 Sekunden. Die Verwendung einer separaten COUNT(*)Abfrage dauerte weniger als 5 Sekunden (für beide Abfragen mit Anzahl + Ergebnisse).
Sam Dufel
9
Sehr interessante Ergebnisse. Da MySQL-Dokumentation ausdrücklich , dass deutet darauf hin , SQL_CALC_FOUND_ROWSschneller sein, frage ich mich , in welchen Situationen (wenn überhaupt) es tatsächlich ist schneller!
Svidgen
12
altes Thema, aber für diejenigen, die noch interessant sind! Ich habe gerade meine Überprüfung von INNODB nach 10 Überprüfungen abgeschlossen. Ich kann feststellen, dass es 26 (2 Abfragen) gegen 9,2 (1 Abfrage) ist. SELECT SQL_CALC_FOUND_ROWS tblA. *, TblB.id AS 'b_id', tblB.city AS 'b_city', tblC.id AS 'C_Id', tblC.type AS 'c_type', tblD.id AS 'D_ID', tblD.extype AS 'd_extype', tblY.id AS 'y_id', tblY.ydt AS y_ydt FROM tblA, tblB, tblC, tblD, , tblY WO tblA.b = tblC.id UND tblA.c = tblB.id UND tblA.d = tblD.id UND tblA.y = tblY.id
Al Po
4
Ich habe gerade dieses Experiment ausgeführt und SQLC_CALC_FOUND_ROWS war viel schneller als zwei Abfragen. Jetzt ist meine Haupttabelle nur noch 65 KB und zwei Verknüpfungen von einigen Hundert, aber die Hauptabfrage dauert mit oder ohne SQLC_CALC_FOUND_ROWS 0,18 Sekunden, aber als ich eine zweite Abfrage mit COUNT ( id) ausführte, dauerte es allein 0,25.
Transilvlad
1
FOUND_ROWS()Beachten Sie zusätzlich zu möglichen Leistungsproblemen, dass dies in MySQL 8.0.17 veraltet ist. Siehe auch die Antwort von @ madhur-bhaiya.
Arueckauer
19

Bei der Auswahl des "besten" Ansatzes ist möglicherweise die Wartbarkeit und Korrektheit Ihres Codes wichtiger als die Geschwindigkeit. In diesem Fall ist SQL_CALC_FOUND_ROWS vorzuziehen, da Sie nur eine einzige Abfrage verwalten müssen. Die Verwendung einer einzelnen Abfrage schließt die Möglichkeit eines geringfügigen Unterschieds zwischen der Haupt- und der Zählabfrage vollständig aus, was zu einer ungenauen Zählung führen kann.

Jeff Clemens
quelle
11
Dies hängt von Ihrer Einrichtung ab. Wenn Sie eine Art ORM oder Abfrage-Generator verwenden, ist es sehr einfach, dieselben Kriterien für beide Abfragen zu verwenden, die Auswahlfelder gegen eine Anzahl auszutauschen und das Limit zu senken. Sie sollten die Kriterien niemals zweimal aufschreiben.
Mpen
Ich möchte darauf hinweisen, dass ich Code lieber mit zwei einfachen, ziemlich standardmäßigen, leicht verständlichen SQL-Abfragen pflegen möchte, als mit einer proprietären MySQL-Funktion - was erwähnenswert ist, ist in neueren MySQL-Versionen veraltet.
Thomasrutter
15

MySQL hat begonnen, die SQL_CALC_FOUND_ROWSFunktionalität ab Version 8.0.17 zu verwerfen.

Es wird daher immer bevorzugt , die Ausführung Ihrer Abfrage mit LIMITund anschließend eine zweite Abfrage mit COUNT(*)und ohne in Betracht zu ziehen, LIMITum festzustellen, ob zusätzliche Zeilen vorhanden sind.

Aus Dokumenten :

Der Abfragemodifikator SQL_CALC_FOUND_ROWS und die zugehörige Funktion FOUND_ROWS () sind ab MySQL 8.0.17 veraltet und werden in einer zukünftigen MySQL-Version entfernt.

COUNT (*) unterliegt bestimmten Optimierungen. SQL_CALC_FOUND_ROWS bewirkt, dass einige Optimierungen deaktiviert werden.

Verwenden Sie stattdessen diese Abfragen:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

Es wurde auch SQL_CALC_FOUND_ROWSbeobachtet, dass allgemein mehr Probleme auftreten, wie in MySQL WL # 12615 erläutert :

SQL_CALC_FOUND_ROWS weist eine Reihe von Problemen auf. Erstens ist es langsam. Häufig ist es billiger, die Abfrage mit LIMIT und dann mit einem separaten SELECT COUNT ( ) für dieselbe Abfrage auszuführen , da COUNT ( ) Optimierungen verwenden kann, die bei der Suche nach der gesamten Ergebnismenge (z. B. Dateisortierung) nicht möglich sind kann für COUNT (*) übersprungen werden, während bei CALC_FOUND_ROWS einige Dateisortierungsoptimierungen deaktiviert werden müssen, um das richtige Ergebnis zu gewährleisten.)

Noch wichtiger ist, dass die Semantik in einer Reihe von Situationen sehr unklar ist. Insbesondere wenn eine Abfrage mehrere Abfrageblöcke enthält (z. B. mit UNION), gibt es einfach keine Möglichkeit, die Anzahl der Zeilen zu berechnen, die gleichzeitig mit der Erstellung einer gültigen Abfrage vorhanden gewesen wären. Während der Iterator-Executor auf diese Art von Abfragen zusteuert, ist es wirklich schwierig, die gleiche Semantik beizubehalten. Wenn die Abfrage mehrere LIMITs enthält (z. B. für abgeleitete Tabellen), ist nicht unbedingt klar, auf welche davon SQL_CALC_FOUND_ROWS verweisen soll. Daher erhalten solche nicht trivialen Abfragen im Iterator-Executor notwendigerweise eine andere Semantik als zuvor.

Schließlich sollten die meisten Anwendungsfälle, in denen SQL_CALC_FOUND_ROWS nützlich erscheint, einfach durch andere Mechanismen als LIMIT / OFFSET gelöst werden. Beispielsweise sollte ein Telefonbuch nach Buchstaben (sowohl in Bezug auf UX als auch in Bezug auf die Indexnutzung) und nicht nach Datensatznummer paginiert werden. Diskussionen werden zunehmend nach Datum sortiert (was wiederum die Verwendung des Index ermöglicht), nicht nach Postnummer. Und so weiter.

Madhur Bhaiya
quelle
Wie führe ich diese beiden Auswahlen als atomare Operation durch? Was ist, wenn jemand vor der Abfrage SELECT COUNT (*) eine Zeile einfügt? Vielen Dank.
Dom
@Dom Wenn Sie über MySQL8 + verfügen, können Sie beide Abfragen mithilfe der Fensterfunktionen in einer einzigen Abfrage ausführen. Dies ist jedoch keine optimale Lösung, da Indizes nicht ordnungsgemäß verwendet werden. Eine andere Möglichkeit besteht darin, diese beiden Abfragen mit LOCK TABLES <tablename>und zu umgeben UNLOCK TABLES. Die dritte Option und (meiner Meinung nach am besten) besteht darin, die Paginierung zu überdenken. Bitte lesen Sie: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya
14

Laut folgendem Artikel: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Wenn Sie einen INDEX für Ihre where-Klausel haben (wenn in Ihrem Fall die ID indiziert ist), ist es besser, nicht SQL_CALC_FOUND_ROWS zu verwenden und stattdessen zwei Abfragen zu verwenden, aber wenn Sie keinen Index für das haben, was Sie in Ihre where-Klausel einfügen (ID in Ihrem Fall) Die Verwendung von SQL_CALC_FOUND_ROWS ist dann effizienter.

patapouf_ai
quelle
8

IMHO, der Grund, warum 2 Anfragen

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

sind schneller als mit SQL_CALC_FOUND_ROWS

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

muss als besonderer Fall gesehen werden.

Tatsächlich hängt es von der Selektivität der WHERE-Klausel im Vergleich zur Selektivität der impliziten Klausel ab, die der ORDER + LIMIT entspricht.

Wie Arvids in einem Kommentar ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ) sagte , die Tatsache, dass die EXPLAIN verwenden oder nicht. Eine temporäre Tabelle sollte eine gute Grundlage sein, um zu wissen, ob SCFR schneller ist oder nicht.

Aber wie ich hinzugefügt habe ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), hängt das Ergebnis wirklich wirklich vom Fall ab. Für einen bestimmten Paginator könnten Sie zu dem Schluss kommen, dass „für die drei ersten Seiten zwei Abfragen verwendet werden; Verwenden Sie für die folgenden Seiten einen SCFR “!

Pierre-Olivier Vares
quelle
6

Entfernen Sie unnötiges SQL und gehen Sie dann COUNT(*)schneller als SQL_CALC_FOUND_ROWS. Beispiel:

SELECT Person.Id, Person.Name, Job.Description, Card.Number
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
LEFT JOIN Card ON Card.Person_Id = Person.Id
WHERE Job.Name = 'WEB Developer'
ORDER BY Person.Name

Dann zählen Sie ohne unnötigen Teil:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'
Jessé Catrinck
quelle
3

Es gibt andere Optionen für das Benchmarking:

1.) Eine Fensterfunktion gibt die tatsächliche Größe direkt zurück (getestet in MariaDB):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2.) Wenn Benutzer über den Tellerrand hinaus denken, müssen sie die genaue Größe der Tabelle meistens nicht kennen. Eine ungefähre Größe ist oft gut genug.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Code4R7
quelle