Qual è il più veloce? SELEZIONA SQL_CALC_FOUND_ROWS FROM `table` o SELECT COUNT (*)


176

Quando si limita il numero di righe che devono essere restituite da una query SQL, generalmente utilizzate nel paging, esistono due metodi per determinare il numero totale di record:

Metodo 1

Includi l' SQL_CALC_FOUND_ROWSopzione nell'originale SELECT, quindi ottieni il numero totale di righe eseguendo SELECT FOUND_ROWS():

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

Metodo 2

Eseguire la query normalmente e quindi ottenere il numero totale di righe eseguendo SELECT COUNT(*)

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

Qual è il metodo migliore / più veloce?

Risposte:


120

Dipende. Vedi il post di MySQL Performance Blog su questo argomento: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Solo un breve riassunto: Peter dice che dipende dai tuoi indici e da altri fattori. Molti dei commenti al post sembrano dire che SQL_CALC_FOUND_ROWS è quasi sempre più lento - a volte fino a 10 volte più lento - rispetto all'esecuzione di due query.


27
Posso confermare questo: ho appena aggiornato una query con 4 join su un database di 168.000 righe. Selezionando solo le prime 100 righe con una SQL_CALC_FOUND_ROWSdurata di oltre 20 secondi; l'utilizzo di una COUNT(*)query separata ha richiesto meno di 5 secondi (per entrambe le query count + results).
Sam Dufel,

9
Risultati molto interessanti. Poiché la documentazione di MySQL suggerisce esplicitamente che SQL_CALC_FOUND_ROWSsarà più veloce, mi chiedo in quali situazioni (se ce ne sono) in realtà è più veloce!
svidgen,

12
vecchio argomento, ma per chi è ancora interessante! Ho appena finito il mio controllo su INNODB da 10 controlli. Posso dire che è 26 (2 query) contro 9.2 (1 query) SELEZIONARE SQL_CALC_FOUND_ROWS tblA. *, TblB.id AS 'b_id', tblB.city AS 'b_city', tblC.id AS 'c_id', 'c_type' tblC.type AS, 'd_id' tblD.id AS, 'd_extype' tblD.extype AS, 'y_id' tblY.id AS, tblY.ydt AS y_ydt DA tblA, tblB, tblC, tblD, tblY DOVE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
Ho appena eseguito questo esperimento e SQLC_CALC_FOUND_ROWS è stato molto più veloce di due query. Ora la mia tabella principale è solo 65k e due join di poche centinaia, ma la query principale richiede 0,18 secondi con o senza SQLC_CALC_FOUND_ROWS ma quando ho eseguito una seconda query con COUNT ( id) ci sono voluti solo 0,25.
transilvlad

1
Oltre ai possibili problemi di prestazioni, considera che FOUND_ROWS()è stato deprecato in MySQL 8.0.17. Vedi anche la risposta di @ madhur-bhaiya.
Arueckauer,

19

Quando si sceglie l'approccio "migliore", una considerazione più importante della velocità potrebbe essere la manutenibilità e la correttezza del codice. In tal caso, è preferibile SQL_CALC_FOUND_ROWS perché è necessario mantenere un'unica query. L'uso di una singola query preclude completamente la possibilità di una sottile differenza tra le query principali e quelle di conteggio, che può portare a un COUNT impreciso.


11
Questo dipende dalla tua configurazione. Se stai usando un qualche tipo di ORM o query builder, è molto facile usare lo stesso dove criteri per entrambe le query, scambiare i campi selezionati con un conteggio e rilasciare il limite. Non dovresti mai scrivere i criteri due volte.
mpen

Vorrei sottolineare che preferirei mantenere il codice utilizzando due semplici query SQL abbastanza standard e di facile comprensione rispetto a una che utilizza una funzionalità proprietaria di MySQL - che vale la pena notare è deprecata nelle nuove versioni di MySQL.
thomasrutter,

15

MySQL ha iniziato a deprecare la SQL_CALC_FOUND_ROWSfunzionalità con la versione 8.0.17 in poi.

Pertanto, è sempre preferibile considerare di eseguire la query con LIMIT, quindi una seconda query con COUNT(*)e senza LIMITdeterminare se vi sono righe aggiuntive.

Da documenti :

Il modificatore di query SQL_CALC_FOUND_ROWS e la relativa funzione FOUND_ROWS () sono obsoleti a partire da MySQL 8.0.17 e verranno rimossi in una versione futura di MySQL.

COUNT (*) è soggetto a determinate ottimizzazioni. SQL_CALC_FOUND_ROWS causa la disabilitazione di alcune ottimizzazioni.

Utilizzare invece queste query:

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

Inoltre, SQL_CALC_FOUND_ROWSè stato osservato di avere più problemi in generale, come spiegato nel MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS presenta numerosi problemi. Innanzitutto è lento. Spesso, sarebbe più economico eseguire la query con LIMIT e quindi un SELECT COUNT ( ) separato per la stessa query, poiché COUNT ( ) può fare uso di ottimizzazioni che non possono essere eseguite durante la ricerca dell'intero set di risultati (ad es. Filesort può essere ignorato per COUNT (*), mentre con CALC_FOUND_ROWS, dobbiamo disabilitare alcune ottimizzazioni di filesort per garantire il giusto risultato)

Ancora più importante, ha una semantica molto poco chiara in una serie di situazioni. In particolare, quando una query ha più blocchi di query (ad es. Con UNION), semplicemente non esiste alcun modo per calcolare il numero di righe "potenziali" contemporaneamente alla produzione di una query valida. Poiché l'esecutore iteratore sta procedendo verso questo tipo di query, è davvero difficile provare a mantenere la stessa semantica. Inoltre, se nella query sono presenti più LIMIT (ad esempio per le tabelle derivate), non è necessariamente chiaro a quale di questi SQL_CALC_FOUND_ROWS debba fare riferimento. Pertanto, tali query non banali otterranno necessariamente una semantica diversa nell'esecutore iteratore rispetto a ciò che avevano prima.

Infine, la maggior parte dei casi d'uso in cui SQL_CALC_FOUND_ROWS sembrerebbe utile dovrebbe essere semplicemente risolta con meccanismi diversi da LIMIT / OFFSET. Ad esempio, un elenco telefonico deve essere impaginato per lettera (sia in termini di UX sia in termini di utilizzo dell'indice), non in base al numero di record. Le discussioni sono sempre più infinite e sono ordinate per data (consentendo nuovamente l'indice), non per impaginazione per numero postale. E così via.


Come eseguire queste due selezioni come operazione atomica? Cosa succede se qualcuno inserisce una riga prima della query SELECT COUNT (*)? Grazie.
Dom

@Dom se hai MySQL8 +, puoi eseguire entrambe le query in una singola query usando le funzioni di Windows; ma questa non sarà una soluzione ottimale in quanto gli indici non verranno utilizzati correttamente. Un'altra opzione è quella di circondare queste due query con LOCK TABLES <tablename>e UNLOCK TABLES. La terza opzione e (miglior IMHO) è ripensare l'impaginazione. Si prega di leggere: mariadb.com/kb/it/library/pagination-optimization
Madhur Bhaiya,


8

IMHO, il motivo per cui 2 query

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

sono più veloci dell'uso di SQL_CALC_FOUND_ROWS

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

deve essere visto come un caso particolare.

Dipende in effetti dalla selettività della clausola WHERE rispetto alla selettività di quella implicita equivalente a ORDER + LIMIT.

Come ha commentato Arvids nel commento ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), il fatto che EXPLAIN usi o meno, una tabella temporanea, dovrebbe essere una buona base per sapere se SCFR sarà più veloce o meno.

Ma, come ho aggiunto ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), il risultato dipende molto dal caso. Per un particolare impaginatore, potresti arrivare alla conclusione che “per le 3 prime pagine, usa 2 query; per le pagine seguenti, utilizzare un SCFR ”!


6

Rimozione di alcuni SQL non necessari e quindi COUNT(*)sarà più veloce di SQL_CALC_FOUND_ROWS. Esempio:

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

Quindi contare senza parti non necessarie:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'

3

Esistono altre opzioni per il benchmark:

1.) Una funzione di finestra restituirà direttamente la dimensione effettiva (testata in MariaDB):

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

2.) Pensando fuori dagli schemi, la maggior parte delle volte gli utenti non hanno bisogno di conoscere la dimensione ESATTA della tabella, un approssimativo è spesso abbastanza buono.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.