MySQL: il modo più veloce per contare il numero di righe


117

Quale modo per contare un numero di righe dovrebbe essere più veloce in MySQL?

Questo:

SELECT COUNT(*) FROM ... WHERE ...

Oppure, l'alternativa:

SELECT 1 FROM ... WHERE ...

// and then count the results with a built-in function, e.g. in PHP mysql_num_rows()

Si potrebbe pensare che il primo metodo dovrebbe essere più veloce, poiché questo è chiaramente il territorio del database e il motore del database dovrebbe essere più veloce di chiunque altro quando si determinano internamente cose di questo tipo.


1
Oh, ho trovato una domanda simile ( stackoverflow.com/questions/1855226/… ). Ma poi, io uso SELECT 1e non SELECT *. C'è una differenza?
Franz

non lo so, ma è concepibile che queste due risposte siano identiche: l'ottimizzatore di query mysql può fare la stessa cosa su ciascuna. detto questo, il primo è meno ambiguo del secondo. perché non scrivi alcuni benchmark e lo provi?
Jesse Cohen

Uhm, supponiamo che io stia cercando di migliorare la visibilità del motore di ricerca di SO ponendo una domanda simile con parole diverse;)
Franz

1
La differenza è la quantità di dati inviati al lato PHP. Più colonne hai, più lento SELECT * diventa relativo a SELECT 1, perché vengono recuperate tutte le colonne invece del solo numero 1. Quando esegui mysql_query(), ad esempio, l'intero set di risultati viene inviato a PHP da MySQL, indipendentemente da ciò che tu fare con quei dati.
toon81

Fare una domanda come questa è un ottimo modo per ottenere informazioni o nuove idee, ma alla fine se hai effettivamente uno scenario specifico in cui desideri più velocità, dovrai eseguire dei test per vedere qual è il più veloce.
still_dreaming_1

Risposte:


124

Quando si COUNT(*)prende in conto gli indici delle colonne, quindi sarà il miglior risultato. Mysql con MyISAM motore memorizza effettivamente il conteggio delle righe, non conta tutte le righe ogni volta che provi a contare tutte le righe. (in base alla colonna della chiave primaria)

Usare PHP per contare le righe non è molto intelligente, perché devi inviare dati da mysql a php. Perché farlo quando puoi ottenere lo stesso dal lato mysql?

Se COUNT(*)è lento, dovresti eseguire EXPLAINla query e controllare se gli indici sono realmente utilizzati e dove dovrebbero essere aggiunti.


Quanto segue non è il modo più veloce , ma c'è un caso in cui COUNT(*)non si adatta davvero: quando inizi a raggruppare i risultati, puoi incorrere in un problema, in cui COUNTnon conta tutte le righe.

La soluzione è SQL_CALC_FOUND_ROWS. Questo viene solitamente utilizzato quando si selezionano le righe ma è comunque necessario conoscere il conteggio totale delle righe (ad esempio, per la paginazione). Quando selezioni le righe di dati, aggiungi la SQL_CALC_FOUND_ROWSparola chiave dopo SELEZIONA:

SELECT SQL_CALC_FOUND_ROWS [needed fields or *] FROM table LIMIT 20 OFFSET 0;

Dopo aver selezionato le righe necessarie, puoi ottenere il conteggio con questa singola query:

SELECT FOUND_ROWS();

FOUND_ROWS() deve essere chiamato immediatamente dopo la query di selezione dei dati.


In conclusione, tutto si riduce in realtà a quante voci hai e cosa c'è nell'istruzione WHERE. Dovresti davvero prestare attenzione a come vengono utilizzati gli indici, quando ci sono molte righe (decine di migliaia, milioni e oltre).


14
Correzione: MyISAMmemorizza il conteggio delle righe. Altri motori di archiviazione come InnoDB non archiviano i conteggi delle righe e conteranno tutte le righe ogni volta .
The Scrum Meister

1
Sai quale sarà il più veloce quando vuoi semplicemente scoprire se c'è una riga: SELECT 1 FROM ... LIMIT 1o SELECT COUNT(*) FROM ...?
Franz

1
Probabilmente è utile notare che se hai comunque bisogno dei dati e vuoi solo un conteggio per l'impaginazione / ecc. è più efficiente ottenere i dati quindi contare le righe nel programma.
Tyzoid

6
È irrilevante se il motore memorizza i conteggi delle righe. La domanda afferma chiaramente che c'è una WHEREclausola.
Álvaro González

1
@Franz SELECT COUNT(*) FROM ...può richiedere molto tempo, a seconda di ciò che deve essere scansionato (ad esempio una tabella molto grande o un indice di milioni / miliardi / trilioni di righe). SELECT 1 FROM ... LIMIT 1ritorna immediatamente perché lo stai limitando alla prima riga.
jbo5112

59

Dopo aver parlato con i miei compagni di squadra, Ricardo ci ha detto che il modo più veloce è:

show table status like '<TABLE NAME>' \G

Ma devi ricordare che il risultato potrebbe non essere esatto.

Puoi usarlo anche dalla riga di comando:

$ mysqlshow --status <DATABASE> <TABLE NAME>

Maggiori informazioni: http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html

E puoi trovare una discussione completa su mysqlperformanceblog


2
Per InnoDB, questa è un'approssimazione.
Martin Tournoij

2
Questo è ottimo da sapere quando è necessaria un'idea approssimativa del numero di righe in tabelle molto grandi in cui count (*) può letteralmente richiedere ore!
Mark Hansen

Questo mi ha salvato dal strapparmi tutti i capelli. COUNT (*) impiegava anni a contare tutti i 33 milioni di righe nel mio database. Ad ogni modo, volevo solo sapere se la mia funzione di eliminazione delle righe parallele funzionava o meno. Non avevo bisogno di un numero esatto.
joemar.ct

1
+1 L'utilizzo dello stato della tabella al posto di "COUNT (*)" dovrebbe essere la risposta corretta a questa domanda poiché parla di "più veloce" non "precisione".
lepe

2
L'uso SHOW TABLE STATUS(o l'equivalente SELECTin information_schema) è veloce, ma non gestisce una WHEREclausola. È preciso per MyISAM, ma impreciso (a volte fuori di un fattore 2) per InnoDB.
Rick James

29

Ottima domanda, ottime risposte. Ecco un modo rapido per ripetere i risultati se qualcuno sta leggendo questa pagina e manca quella parte:

$counter = mysql_query("SELECT COUNT(*) AS id FROM table");
$num = mysql_fetch_array($counter);
$count = $num["id"];
echo("$count");

5
mysql_query è una funzione deprecata a partire da PHP 5.5.0.
Omar Tariq

8
Perché no as count? idè fonte di confusione al primo sguardo.
Orkhan Alikhanov

Non risponde alla domanda
mentalic

17

Questa query (che è simile a quella pubblicata da bayuah ) mostra un bel riepilogo di tutte le tabelle conteggiate all'interno di un database: (versione semplificata della stored procedure di Ivan Cachicatari che consiglio vivamente).

SELECT TABLE_NAME AS 'Table Name', TABLE_ROWS AS 'Rows' FROM information_schema.TABLES WHERE TABLES.TABLE_SCHEMA = '`YOURDBNAME`' AND TABLES.TABLE_TYPE = 'BASE TABLE'; 

Esempio:

+-----------------+---------+
| Table Name      | Rows    |
+-----------------+---------+
| some_table      |   10278 |
| other_table     |     995 |

Mi dà un risultato. Ma i risultati del conteggio (1) e di questo sono diversi. In questo modo fornisce sempre un numero inferiore a quello della query di conteggio. qualche idea?
Ayyappan Sekar

3
Solo una nota ai lettori. Questo metodo è estremamente veloce ma è applicabile solo quando è possibile lavorare con un numero approssimativo di righe poiché il valore memorizzato in information_schemanon è lo stesso di quello restituito da SELECT count(*) FROMnel caso in cui venga utilizzato InnoDB. Se hai bisogno di un valore rigoroso, tieni presente che questo metodo fornisce un valore rigoroso solo con le tabelle MyISAM. Con InnoDB il numero di righe è approssimativo.
Bartosz Firyn

13

Ho sempre capito che quanto segue mi darà i tempi di risposta più rapidi.

SELECT COUNT(1) FROM ... WHERE ...

1
SELEZIONARE 1 DA ... DOVE ... non sarebbe ancora più veloce?
patrick

3
@patrick - SELECT 1 ...restituirà tante righe quante sono richieste da WHEREe LIMITe saranno tutte "1".
Rick James

1
show table status like '<TABLE NAME>' Questo sarà molto più veloce.
profondo

@deep - ma non rilevante se hai una WHEREclausola. E, per InnoDB, è solo una stima.
Rick James

@RickJames si vero!
profondo

6

Se è necessario ottenere il conteggio dell'intero set di risultati, è possibile adottare il seguente approccio:

SELECT SQL_CALC_FOUND_ROWS * FROM table_name LIMIT 5;
SELECT FOUND_ROWS();

Normalmente non è più veloce dell'utilizzo COUNT anche se si potrebbe pensare che sia vero il contrario perché esegue il calcolo internamente e non invia i dati all'utente, quindi si sospetta il miglioramento delle prestazioni.

L'esecuzione di queste due query è utile per l'impaginazione per ottenere i totali ma non particolarmente per l'utilizzo di WHEREclausole.


Intersting. Funziona con i sistemi di database più comuni? MySQL, Postgres, SQLite ...?
Franz

4
In realtà spesso non è affatto più veloce dell'utilizzo di COUNT (*). Vedere stackoverflow.com/questions/186588/...
toon81

2
Dovresti stare MOLTO attento quando usi questa funzione. Il suo uso sconsiderato una volta ha fermato il nostro intero ambiente di produzione. È MOLTO dispendioso in termini di risorse, quindi usalo con cura.
Janis Peisenieks

6

Ho fatto alcuni benchmark per confrontare il tempo di esecuzione di COUNT(*)vsCOUNT(id) (id è la chiave primaria della tabella - indicizzata).

Numero di prove: 10 * 1000 query

risultati: COUNT(*) è più veloce del 7%

VISUALIZZA GRAFICO: benchmarkgraph

Il mio consiglio è di utilizzare: SELECT COUNT(*) FROM table


1
Cordiali saluti, c'è anche un modo comune con cui contare COUNT(1), sarebbe interessante vedere alcuni benchmark lì ...
Sliq

4

Prova questo:

SELECT
    table_rows "Rows Count"
FROM
    information_schema.tables
WHERE
    table_name="Table_Name"
AND
    table_schema="Database_Name";

@lepe mi dispiace. Voglio dire, è davvero bello se qualcuno che ha votato per difetto desse qualche spiegazione perché lo ha fatto, così tutti possono imparare qualcosa al riguardo.
bayuah

1
Questo ti darà rapidamente una risposta approssimativa . Se hai bisogno di una risposta esatta, devi eseguire select count(*) from table_nameo qualcos'altro. dba.stackexchange.com/questions/151769/…
Programster

@ Programmatore Grazie. È meglio che lasciarmi all'oscuro per quasi un anno.
bayuah

1
@bayuah non sono sicuro di cosa intendevi con il tuo ultimo commento. Posso solo presumere che tu pensi che io sia quello che ha scartato la tua risposta, cosa che non sono.
Programster

1
@ Programmatore No, mi dispiace, non intendevo questo. Volevo dire grazie per la tua spiegazione, quindi posso congetturare cosa forse pensava Downvoter quando lo ha fatto.
bayuah

3

Forse potresti prendere in considerazione l'idea di fare un file SELECT max(Id) - min(Id) + 1. Funzionerà solo se i tuoi ID sono sequenziali e le righe non vengono eliminate. È comunque molto veloce.


3
Fai attenzione: i server a volte utilizzano un valore di incremento automatico> 1 (per motivi di backup), quindi questa soluzione è buona ma dovresti prima controllare la configurazione del database.
Alex

1

EXPLAIN SELECT id FROM ....ha fatto il trucco per me. e ho potuto vedere il numero di righe sotto la rowscolonna del risultato.


0

Gestivo tabelle per il governo tedesco con a volte 60 milioni di record.

E dovevamo conoscere molte volte le righe totali.

Così noi programmatori di database abbiamo deciso che in ogni tabella c'è sempre il record uno in cui è memorizzato il numero totale di record. Abbiamo aggiornato questo numero, a seconda delle righe INSERT o DELETE.

Abbiamo provato tutti gli altri modi. Questo è di gran lunga il modo più veloce.


1
e quali sono i dettagli di come hai aggiornato quella riga? Il che significa però un progetto difettoso per un tavolo, dove tutte le righe richiederebbero un int sprecato per venire avanti per il viaggio.
Drew

5
Sì, è davvero stupido ahah. Con ogni query devi ignorare la prima riga. Creerei semplicemente una tabella dei totali e la popolerei in base a un trigger. Tabella utenti all'inserimento, aggiorna la tabella dei totali. Tabella utenti all'eliminazione, aggiornamento della tabella dei totali.
HTMLGuy

-1

Un'istruzione count (*) con una condizione where sulla chiave primaria ha restituito il conteggio delle righe molto più velocemente per me, evitando la scansione completa della tabella.

SELECT COUNT(*) FROM ... WHERE <PRIMARY_KEY> IS NOT NULL;

Questo è stato molto più veloce per me di

SELECT COUNT(*) FROM ...
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.