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.