Który jest najszybszy? SELECT SQL_CALC_FOUND_ROWS FROM `table` lub SELECT COUNT (*)


176

Ograniczając liczbę wierszy zwracanych przez zapytanie SQL, zwykle używaną w stronicowaniu, istnieją dwie metody określania łącznej liczby rekordów:

Metoda 1

Uwzględnij SQL_CALC_FOUND_ROWSopcję w oryginale SELECT, a następnie uzyskaj łączną liczbę wierszy, uruchamiając SELECT FOUND_ROWS():

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

Metoda 2

Uruchom zapytanie normalnie, a następnie uzyskaj łączną liczbę wierszy, uruchamiając SELECT COUNT(*)

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

Która metoda jest najlepsza / najszybsza?

Odpowiedzi:


120

To zależy. Zobacz post na blogu MySQL Performance na ten temat: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Krótkie podsumowanie: Piotr mówi, że to zależy od twoich indeksów i innych czynników. Wiele komentarzy do posta wydaje się mówić, że SQL_CALC_FOUND_ROWS jest prawie zawsze wolniejszy - czasem nawet 10 razy wolniejszy - niż uruchomienie dwóch zapytań.


27
Mogę to potwierdzić - właśnie zaktualizowałem zapytanie z 4 złączeniami w bazie danych 168 000 wierszy. Wybranie tylko pierwszych 100 wierszy SQL_CALC_FOUND_ROWSzajęło ponad 20 sekund; użycie osobnego COUNT(*)zapytania zajęło mniej niż 5 sekund (w przypadku zapytań licznik + wyniki).
Sam Dufel,

9
Bardzo ciekawe ustalenia. Ponieważ dokumentacja MySQL wyraźnie sugeruje, że SQL_CALC_FOUND_ROWSbędzie to szybsze, zastanawiam się, w jakich sytuacjach (jeśli w ogóle) faktycznie jest szybsze!
svidgen,

12
stary temat, ale dla tych, którzy wciąż są ciekawi! Właśnie skończyłem sprawdzać INNODB z 10 sprawdzeń, które mogę powiedzieć, że to 26 (2query) w porównaniu z 9.2 (1 zapytanie) SELECT 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 jak, tblY.ydt CO y_ydt Z tblA, tblB, tblC, tblD, tblY GDZIE tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Al Po

4
Właśnie przeprowadziłem ten eksperyment i SQLC_CALC_FOUND_ROWS był znacznie szybszy niż dwa zapytania. Teraz moja główna tabela ma tylko 65k i dwa sprzężenia z kilkuset, ale główne zapytanie zajmuje 0,18 sekundy z lub bez SQLC_CALC_FOUND_ROWS, ale kiedy wykonałem drugie zapytanie z COUNT ( id), zajęło tylko 0,25.
transilvlad

1
Oprócz możliwych problemów z wydajnością, należy wziąć pod uwagę, że FOUND_ROWS()w MySQL 8.0.17 jest to przestarzałe. Zobacz także odpowiedź @ madhur-bhaiya.
arueckauer

19

Wybierając „najlepsze” podejście, ważniejszą kwestią niż szybkość może być łatwość utrzymania i poprawność kodu. Jeśli tak, SQL_CALC_FOUND_ROWS jest preferowany, ponieważ potrzebujesz tylko jednego zapytania. Użycie pojedynczego zapytania całkowicie wyklucza możliwość subtelnej różnicy między zapytaniem głównym i licznikiem, co może prowadzić do niedokładnego COUNT.


11
To zależy od Twojej konfiguracji. Jeśli używasz jakiegoś narzędzia ORM lub konstruktora zapytań, bardzo łatwo jest użyć tego samego, w którym kryteria dla obu zapytań, zamienić pola wyboru na liczbę i zmniejszyć limit. Nigdy nie należy dwukrotnie zapisywać kryteriów.
mpen

Chciałbym zwrócić uwagę, że wolę utrzymywać kod przy użyciu dwóch prostych, dość standardowych, łatwych do zrozumienia zapytań SQL niż jednego, który korzysta z zastrzeżonej funkcji MySQL - co warto zauważyć, jest przestarzałe w nowszych wersjach MySQL.
thomasrutter

15

MySQL zaczął wycofywać swoją SQL_CALC_FOUND_ROWSfunkcjonalność od wersji 8.0.17 i nowszych.

Dlatego zawsze lepiej jest rozważyć wykonanie zapytania za pomocą LIMIT, a następnie drugiego zapytania z COUNT(*)lub bez w LIMITcelu określenia, czy są dodatkowe wiersze.

Z dokumentów :

Modyfikator zapytania SQL_CALC_FOUND_ROWS i towarzysząca mu funkcja FOUND_ROWS () są przestarzałe od MySQL 8.0.17 i zostaną usunięte w przyszłej wersji MySQL.

COUNT (*) podlega pewnym optymalizacjom. SQL_CALC_FOUND_ROWS powoduje wyłączenie niektórych optymalizacji.

Zamiast tego użyj tych zapytań:

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

SQL_CALC_FOUND_ROWSZaobserwowano również, że ogólnie ma więcej problemów, jak wyjaśniono w MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS ma wiele problemów. Przede wszystkim jest powolny. Często byłoby taniej uruchomić zapytanie z LIMIT, a następnie osobnym SELECT COUNT ( ) dla tego samego zapytania, ponieważ COUNT ( ) może korzystać z optymalizacji, których nie można wykonać podczas wyszukiwania całego zestawu wyników (np. Sortowanie plików można pominąć dla COUNT (*), podczas gdy przy CALC_FOUND_ROWS musimy wyłączyć niektóre optymalizacje sortowania plików, aby zagwarantować właściwy wynik)

Co ważniejsze, ma bardzo niejasną semantykę w wielu sytuacjach. W szczególności, gdy zapytanie ma wiele bloków zapytania (np. Z UNION), po prostu nie ma możliwości obliczenia liczby „niedoszłych” wierszy w tym samym czasie, w którym powstaje prawidłowe zapytanie. Ponieważ wykonawca iteratora postępuje w kierunku tego rodzaju zapytań, naprawdę trudno jest spróbować zachować tę samą semantykę. Ponadto, jeśli w zapytaniu występuje wiele LIMITÓW (np. Dla tabel pochodnych), niekoniecznie jest jasne, do którego z nich SQL_CALC_FOUND_ROWS powinien się odnosić. Zatem takie nietrywialne zapytania z konieczności otrzymają inną semantykę w programie wykonującym iterator w porównaniu z tym, co miały wcześniej.

Wreszcie, większość przypadków użycia, w których SQL_CALC_FOUND_ROWS wydaje się przydatna, należy po prostu rozwiązać za pomocą innych mechanizmów niż LIMIT / OFFSET. Np. Książka telefoniczna powinna być paginowana literami (zarówno pod względem UX, jak i wykorzystania indeksu), a nie według numeru rekordu. Dyskusje są coraz częściej przewijane w nieskończoność według daty (znowu pozwala na użycie indeksu), a nie według numeru postu. I tak dalej.


Jak wykonać te dwie selekcje jako operację atomową? Co się stanie, jeśli ktoś wstawi wiersz przed zapytaniem SELECT COUNT (*)? Dzięki.
Dom

@Dom, jeśli masz MySQL8 +, możesz uruchomić oba zapytania w jednym zapytaniu za pomocą funkcji okna; ale nie będzie to optymalne rozwiązanie, ponieważ indeksy nie będą używane prawidłowo. Inną opcją jest otoczenie tych dwóch zapytań znakami LOCK TABLES <tablename>i UNLOCK TABLES. Trzecią opcją i (najlepszym IMHO) jest przemyślenie paginacji. Przeczytaj: mariadb.com/kb/en/library/pagination-optimization
Madhur Bhaiya


8

IMHO, powód, dla którego 2 zapytania

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

są szybsze niż używanie SQL_CALC_FOUND_ROWS

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

należy traktować jako szczególny przypadek.

W rzeczywistości zależy to od selektywności klauzuli WHERE w porównaniu z selektywnością klauzuli niejawnej odpowiadającej ORDER + LIMIT.

Jak powiedział Arvids w komentarzu ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394 ), fakt, że EXPLAIN używa lub nie, tabela tymczasowa powinna być dobrą podstawą do ustalenia, czy SCFR będzie szybszy, czy nie.

Ale, jak dodałem ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), wynik naprawdę zależy od przypadku. W przypadku konkretnego paginatora możesz dojść do wniosku, że „dla 3 pierwszych stron użyj 2 zapytań; na kolejnych stronach użyj SCFR ”!


6

Usunięcie niepotrzebnego kodu SQL i wtedy COUNT(*)będzie szybsze niż SQL_CALC_FOUND_ROWS. Przykład:

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

Następnie policz bez zbędnej części:

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

3

Istnieją inne opcje testów porównawczych:

1.) Funkcja okna bezpośrednio zwraca rzeczywisty rozmiar (testowane w MariaDB):

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

2.) Myśląc po wyjęciu z pudełka, przez większość czasu użytkownicy nie muszą znać DOKŁADNEGO rozmiaru stołu, przybliżenie jest często wystarczające.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.