MySQL: najszybszy sposób zliczania liczby wierszy

117

Który sposób liczenia wierszy powinien być szybszy w MySQL?

To:

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

Lub alternatywą:

SELECT 1 FROM ... WHERE ...

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

Można by pomyśleć, że pierwsza metoda powinna być szybsza, ponieważ jest to wyraźnie terytorium bazy danych, a silnik bazy danych powinien być szybszy niż ktokolwiek inny podczas określania takich rzeczy wewnętrznie.

Franz
źródło
1
Och, znalazłem podobne pytanie ( stackoverflow.com/questions/1855226/ ... ). Ale wtedy używam SELECT 1i nie SELECT *. Czy jest jakaś różnica?
Franz,
nie wiem, ale można sobie wyobrazić, że te dwie odpowiedzi są identyczne - optymalizator zapytań mysql może zrobić to samo na każdej z nich. to powiedziawszy, że pierwsza jest mniej niejednoznaczna niż druga. może napiszesz jakieś testy porównawcze i nie przetestujesz tego?
Jesse Cohen,
Uhm, załóżmy, że próbuję poprawić widoczność w wyszukiwarkach SO, zadając podobne pytanie innymi słowami;)
Franz
1
Różnica polega na ilości danych przesłanych na stronę PHP. Im więcej masz kolumn, tym wolniej SELECT * pobiera względem SELECT 1, ponieważ wszystkie kolumny są pobierane zamiast tylko liczby 1. Kiedy uruchomisz mysql_query(), na przykład, cały zestaw wyników jest wysyłany do PHP z MySQL, niezależnie od tego zrobić z tymi danymi.
toon81
Zadanie takiego pytania to świetny sposób na zdobycie wglądu lub nowych pomysłów, ale ostatecznie, jeśli masz konkretny scenariusz, w którym chcesz uzyskać większą prędkość, będziesz musiał przeprowadzić testy, aby zobaczyć, co jest najszybsze.
still_dreaming_1

Odpowiedzi:

124

Kiedy weźmiesz COUNT(*)zliczanie indeksów kolumn, to będzie to najlepszy wynik. MySQL z silnikiem MyISAM faktycznie przechowuje liczbę wierszy, nie liczy wszystkich wierszy za każdym razem, gdy próbujesz policzyć wszystkie wiersze. (na podstawie kolumny klucza podstawowego)

Używanie PHP do liczenia wierszy nie jest zbyt inteligentne, ponieważ musisz przesyłać dane z mysql do php. Po co to robić, skoro możesz osiągnąć to samo po stronie mysql?

Jeśli COUNT(*)działa wolno, należy uruchomić EXPLAINzapytanie i sprawdzić, czy indeksy są rzeczywiście używane i gdzie należy je dodać.


Poniższe nie jest najszybszym sposobem, ale jest przypadek, w którym COUNT(*)tak naprawdę nie pasuje - kiedy zaczynasz grupować wyniki, możesz napotkać problem, w którym COUNTtak naprawdę nie liczy się wszystkich wierszy.

Rozwiązaniem jest SQL_CALC_FOUND_ROWS. Jest to zwykle używane podczas wybierania wierszy, ale nadal musisz znać całkowitą liczbę wierszy (na przykład do stronicowania). Gdy wybierzesz wiersze danych, po prostu dołącz SQL_CALC_FOUND_ROWSsłowo kluczowe po SELECT:

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

Po wybraniu potrzebnych wierszy możesz uzyskać liczbę za pomocą tego pojedynczego zapytania:

SELECT FOUND_ROWS();

FOUND_ROWS() należy wywołać natychmiast po zapytaniu wybierającym dane.


Podsumowując, wszystko sprowadza się do tego, ile masz wpisów i co znajduje się w instrukcji WHERE. Naprawdę powinieneś zwrócić uwagę na to, jak używane są indeksy, gdy jest dużo wierszy (dziesiątki tysięcy, miliony i więcej).

Mārtiņš Briedis
źródło
14
Korekta: MyISAMprzechowuje liczbę wierszy. Inne mechanizmy przechowywania danych, takie jak InnoDB nie przechowują liczby wierszy i za każdym razem zliczają wszystkie wiersze .
The Scrum Meister,
1
Czy wiesz, co będzie najszybsze, gdy po prostu chcesz się dowiedzieć, czy jest wiersz: SELECT 1 FROM ... LIMIT 1czy SELECT COUNT(*) FROM ...?
Franz
1
Prawdopodobnie warto zauważyć, że jeśli i tak potrzebujesz danych i chcesz liczyć tylko dla paginacji / itp. efektywniej jest uzyskać dane, a następnie policzyć wiersze w programie.
Tyzoid
6
Nie ma znaczenia, czy silnik przechowuje liczbę wierszy. Pytanie wyraźnie stwierdza, że ​​istnieje WHEREklauzula.
Álvaro González
1
@Franz SELECT COUNT(*) FROM ...może zająć dużo czasu, w zależności od tego, co ma zostać zeskanowane (np. Bardzo duża tabela lub indeks milionów / miliardów / bilionów wierszy). SELECT 1 FROM ... LIMIT 1wraca natychmiast, ponieważ ograniczasz go do pierwszego wiersza.
jbo5112
59

Po rozmowie z kolegami z zespołu Ricardo powiedział nam, że najszybszym sposobem jest:

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

Musisz jednak pamiętać, że wynik może nie być dokładny.

Możesz go również użyć z wiersza poleceń:

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

Więcej informacji: http://dev.mysql.com/doc/refman/5.7/en/show-table-status.html

Pełną dyskusję można znaleźć na blogu mysqlperformanceblog

MagMax
źródło
2
W przypadku InnoDB jest to przybliżenie.
Martin Tournoij
2
Dobrze jest wiedzieć, gdy potrzebujesz przybliżonego wyobrażenia o liczbie wierszy w bardzo dużych tabelach, w których count (*) może dosłownie zająć godziny!
Mark Hansen
To uratowało mnie przed wyrywaniem wszystkich włosów. COUNT (*) trwało wieki, aby policzyć wszystkie 33 miliony wierszy w mojej bazie danych. W każdym razie chciałem tylko wiedzieć, czy moja równoległa funkcja usuwania wierszy działa, czy nie. Nie potrzebowałem dokładnej liczby.
joemar.ct
1
+1 Użycie stanu tabeli zamiast „COUNT (*)” powinno być poprawną odpowiedzią na to pytanie, podobnie jak „najszybsza”, a nie „dokładność”.
lepe
2
Użycie SHOW TABLE STATUS(lub odpowiednik SELECTw information_schema) jest szybkie, ale nie obsługuje WHEREklauzuli. Jest dokładny dla MyISAM, ale nieprecyzyjny (czasami o współczynnik 2) dla InnoDB.
Rick James
29

Świetne pytanie, świetne odpowiedzi. Oto szybki sposób na powtórzenie wyników, jeśli ktoś czyta tę stronę i brakuje jej części:

$counter = mysql_query("SELECT COUNT(*) AS id FROM table");
$num = mysql_fetch_array($counter);
$count = $num["id"];
echo("$count");
Dan Horvat
źródło
5
mysql_query jest przestarzałą funkcją w PHP 5.5.0.
Omar Tariq
8
Dlaczego nie as count? idjest zagmatwany na pierwszy rzut oka.
Orkhan Alikhanov
Nie odpowiada na pytanie
mentalny
17

To zapytanie (podobne do tego, które opublikował Bayuah ) pokazuje ładne podsumowanie wszystkich tabel w bazie danych: (uproszczona wersja procedury składowanej autorstwa Ivana Cachicatari, którą bardzo polecam).

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'; 

Przykład:

+-----------------+---------+
| Table Name      | Rows    |
+-----------------+---------+
| some_table      |   10278 |
| other_table     |     995 |
lepe
źródło
Daje mi wynik. Ale wyniki z count (1) są różne. W ten sposób zawsze daje mniejszą liczbę niż zapytanie count. jakieś pomysły?
Ayyappan Sekar
3
Tylko uwaga dla czytelników. Ta metoda jest niezwykle szybka, ale ma zastosowanie tylko wtedy, gdy można pracować z przybliżoną liczbą wierszy, ponieważ wartość przechowywana w information_schemanie jest taka sama, jak wartość zwracana przez SELECT count(*) FROMInnoDB. Jeśli potrzebujesz ścisłej wartości, pamiętaj, że ta metoda daje ścisłą wartość tylko w przypadku tabel MyISAM. W przypadku InnoDB liczba wierszy jest przybliżona.
Bartosz Firyn
13

Zawsze rozumiałem, że poniższe informacje dają mi najszybsze czasy odpowiedzi.

SELECT COUNT(1) FROM ... WHERE ...
adarshr
źródło
1
Czy opcja SELECT 1 FROM ... WHERE ... nie byłaby jeszcze szybsza?
patrick
3
@patrick - SELECT 1 ...powróci w wielu rzędach jak osoby WHEREi LIMITpoprosić, a oni wszystko będzie „1”.
Rick James
1
show table status like '<TABLE NAME>' To będzie znacznie szybsze.
głęboki
@deep - ale nie ma znaczenia, jeśli masz WHEREklauzulę. A dla InnoDB to tylko szacunki.
Rick James
@RickJames yes true!
głęboki
6

Jeśli chcesz uzyskać zliczenie całego zestawu wyników, możesz zastosować następujące podejście:

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

Zwykle nie jest to szybsze niż używanie, COUNTchociaż można by pomyśleć, że jest odwrotnie, ponieważ wykonuje obliczenia wewnętrznie i nie wysyła danych z powrotem do użytkownika, dlatego podejrzewa się poprawę wydajności.

Wykonanie tych dwóch zapytań jest dobre do paginacji w celu uzyskania sum, ale nie jest szczególnie przydatne w przypadku używania WHEREklauzul.

Alex Rashkov
źródło
Ciekawe. Czy to działa w większości popularnych systemów baz danych? MySQL, Postgres, SQLite ...?
Franz
4
W rzeczywistości często nie jest to szybsze niż użycie COUNT (*). Zobacz stackoverflow.com/questions/186588/…
toon81
2
Należy BARDZO ostrożnie używać tej funkcji. Jego lekkomyślne użycie spowodowało kiedyś, że całe nasze środowisko produkcyjne zatrzymało się. Jest BARDZO zasobochłonny, więc używaj go ostrożnie.
Janis Peisenieks
6

Zrobiłem kilka testów porównawczych, aby porównać czas wykonania COUNT(*)vs COUNT(id)(id jest kluczem podstawowym tabeli - indeksowany).

Liczba prób: 10 * 1000 zapytań

Rezultat: COUNT(*)jest szybszy 7%

WIDOK GRAPH: benchmarkgraph

Radzę użyć: SELECT COUNT(*) FROM table

SamuelCarreira
źródło
1
Do Twojej wiadomości istnieje również powszechny sposób liczenia COUNT(1), byłoby interesujące zobaczyć tam kilka testów ...
Sliq
4

Spróbuj tego:

SELECT
    table_rows "Rows Count"
FROM
    information_schema.tables
WHERE
    table_name="Table_Name"
AND
    table_schema="Database_Name";
bayuah
źródło
@lepe Przepraszam. Chodziło mi o to, że to naprawdę miłe, gdyby ktoś, kto przegłosował, wyjaśnił, dlaczego to robi, aby każdy mógł się czegoś o tym dowiedzieć.
bayuah
1
To szybko da ci przybliżoną odpowiedź. Jeśli potrzebujesz dokładnej odpowiedzi, musisz wykonać select count(*) from table_namelub coś innego. dba.stackexchange.com/questions/151769/…
Programster
@Programster Dziękuję. To lepsze niż zostawianie mnie w ciemności przez prawie rok.
bayuah
1
@bayuah Nie jestem pewien, co miałeś na myśli w swoim ostatnim komentarzu. Mogę tylko założyć, że myślisz, że to ja przegłosowałem twoją odpowiedź, a ja nie.
Programster
1
@Programster Nie, przepraszam, nie miałem tego na myśli. Chodziło mi o podziękowania za wyjaśnienie, więc mogę przypuszczać, co może pomyślał Downvoter, kiedy to zrobił.
bayuah
3

Być może warto rozważyć wykonanie SELECT max(Id) - min(Id) + 1. To zadziała tylko wtedy, gdy twoje identyfikatory są sekwencyjne, a wiersze nie zostaną usunięte. Jest jednak bardzo szybki.

sky-dev
źródło
3
Uważaj: serwery czasami używają wartości automatycznej inkrementacji> 1 (ze względu na tworzenie kopii zapasowych), więc to rozwiązanie jest dobre, ale powinieneś najpierw sprawdzić konfigurację bazy danych.
Alex
1

EXPLAIN SELECT id FROM ....załatwił sprawę dla mnie. i mogłem zobaczyć liczbę wierszy pod rowskolumną wyniku.

ssrp
źródło
0

Obsługiwałem tabele dla rządu niemieckiego z czasami 60 milionami rekordów.

I musieliśmy znać wiele razy sumę wierszy.

Tak więc my, programiści baz danych, zdecydowaliśmy, że w każdej tabeli jest rekord jeden zawsze taki rekord, w którym przechowywana jest całkowita liczba rekordów. Zaktualizowaliśmy tę liczbę w zależności od wierszy INSERT lub DELETE.

Próbowaliśmy wszystkich innych sposobów. To zdecydowanie najszybszy sposób.

Scoobeedo Cool
źródło
1
i jakie są szczegóły dotyczące sposobu aktualizacji tego wiersza? Oznacza to, że jest to wadliwy projekt stołu, na którym wszystkie rzędy wymagałyby zmarnowanego intruza, aby jechać.
Drew
5
Tak, to naprawdę głupie haha. Przy każdym zapytaniu musisz zignorować pierwszy wiersz. Po prostu utworzyłbym tabelę sum i zapełnił ją na podstawie wyzwalacza. Tabela użytkowników przy wstawianiu, aktualizacja tabeli podsumowań. Tabela użytkowników podczas usuwania, aktualizuj tabelę podsumowań.
HTMLGuy
-1

Instrukcja count (*) z warunkiem gdzie na kluczu podstawowym zwróciła liczbę wierszy znacznie szybciej, dzięki czemu uniknąłem pełnego skanowania tabeli.

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

To było dla mnie znacznie szybsze niż

SELECT COUNT(*) FROM ...
ayakout
źródło