W przypadku części serwisów konieczne jest podzielenie treści na strony. Można chcieć np. wyświetlać artykuły po 10 na stronie, może chodzić o podzielenie wyników wyszukiwania produktów w sklepie itp. Dobrze jest też mieć możliwość podać użytkownikowi, ile jest łącznie wyników – coś w rodzaju „wyświetlam 1-10 artykułów z 123”. Do tego typu sytuacji MySQL udostępnia funkcję FOUND_ROWS(), która w połączeniu z dyrektywą SQL_CALC_FOUND_ROWS umożliwia sprawdzenie ile rekordów zwraca dane zapytanie, jeśli nie będzie pod uwagę brana wartość LIMIT. Czyli wykonujemy zapytania:

SELECT SQL_CALC_FOUND_ROWS * FROM  artykul WHERE kategoria=10 LIMIT 10;
SELECT FOUND_ROWS();

Pierwsze z nich zwraca treść 10 pierwszych artykułów, a także zlicza ile artykułów znajduje się w kategorii 10. Wynik tych obliczeń zwraca drugie zapytanie, które już nie odwołuje się do danych w tabeli. Teoretycznie te dwa zapytania można zastąpić dwoma następującymi:

SELECT  * FROM artykul WHERE kategoria=10 LIMIT 10;
SELECT COUNT(*) FROM  artykul WHERE kategoria=10;

Które rozwiązanie jest szybsze? Sprawdźmy. Utworzymy trzy tabele, o identycznej strukturze.

mysql>  show create table tab1\G
*************************** 1. row  ***************************
Table: tab1
Create Table:  CREATE TABLE `tab1` (
`a` int(10) NOT NULL AUTO_INCREMENT,
`b`  int(10) NOT NULL,
`c` int(10) NOT NULL,
`d` varchar(32) NOT  NULL,
PRIMARY KEY (`a`),
KEY `bc` (`b`,`c`)
) ENGINE=MyISAM  AUTO_INCREMENT=10000001 DEFAULT CHARSET=latin2
1 row in set (0.00  sec)

Do każdej wrzuconych zostało 10 milionów rekordów. Sprawdźmy jak to wygląda w praktyce.

mysql> SELECT  SQL_NO_CACHE  SQL_CALC_FOUND_ROWS * FROM tab1 LEFT OUTER JOIN tab2 USING  (a) WHERE tab1.b=222 AND a != 5223 GROUP BY tab1.c ORDER BY tab1.d  LIMIT 10;
+-------+-----+----+----------------------------------+------+------+----------------------------------+
|  a     | b   | c  | d                                | b    | c    |  d                                |
+-------+-----+----+----------------------------------+------+------+----------------------------------+
|  34223 | 222 |  2 | 01f11b1dc7251b4dad589e664e28aaf6 |  222 |    6 |  01f11b1dc7251b4dad589e664e28aaf6 |
| 14223 | 222 |  8 |  164f545c22e17e5e9298b1c84b9e3e1e |  222 |    1 |  164f545c22e17e5e9298b1c84b9e3e1e |
| 21223 | 222 |  6 |  2d5b1c6a842b1f669d976472cc52e371 |  222 |    4 |  2d5b1c6a842b1f669d976472cc52e371 |
| 37223 | 222 |  0 |  2f861ba9e11c7e15810214b1d17c4802 |  222 |    1 |  2f861ba9e11c7e15810214b1d17c4802 |
|  9223 | 222 |  1 |  497d0b20f66cebdedc7935e3ffd46efa |  222 |    5 |  497d0b20f66cebdedc7935e3ffd46efa |
|  8223 | 222 |  3 |  532923f11ac97d3e7cb0130315b067dc |  222 |    1 |  532923f11ac97d3e7cb0130315b067dc |
| 17223 | 222 | 10 |  5e38d982aecea3f5fa249828e8f1548a |  222 |    1 |  5e38d982aecea3f5fa249828e8f1548a |
| 15223 | 222 |  7 |  7d4a769562e3528950e2d1aebdfb0550 |  222 |    2 |  7d4a769562e3528950e2d1aebdfb0550 |
|  2223 | 222 |  9 |  934b535800b1cba8f96a5d72f72f1611 |  222 |    2 |  934b535800b1cba8f96a5d72f72f1611 |
|   223 | 222 |  5 |  bcbe3365e6ac95ea2c0343a2395834dd |  222 |    7 |  bcbe3365e6ac95ea2c0343a2395834dd |
+-------+-----+----+----------------------------------+------+------+----------------------------------+
10  rows in set (0.12 sec)

mysql> SELECT FOUND_ROWS();
+--------------+
|  FOUND_ROWS() |
+--------------+
|           11 |
+--------------+
1  row in set (0.00 sec)

Jak widać, zamykamy się w 0,12 sekundy. Rozwiązanie wykorzystujące COUNT(*) wyglądałoby następująco:

mysql>  SELECT SQL_NO_CACHE  * FROM tab1 LEFT OUTER JOIN tab2 USING (a) WHERE  tab1.b=222 AND a != 5223 GROUP BY tab1.c ORDER BY tab1.d LIMIT 10;
+-------+-----+----+----------------------------------+------+------+----------------------------------+
|  a     | b   | c  | d                                | b    | c    |  d                                |
+-------+-----+----+----------------------------------+------+------+----------------------------------+
|  34223 | 222 |  2 | 01f11b1dc7251b4dad589e664e28aaf6 |  222 |    6 |  01f11b1dc7251b4dad589e664e28aaf6 |
| 14223 | 222 |  8 |  164f545c22e17e5e9298b1c84b9e3e1e |  222 |    1 |  164f545c22e17e5e9298b1c84b9e3e1e |
| 21223 | 222 |  6 |  2d5b1c6a842b1f669d976472cc52e371 |  222 |    4 |  2d5b1c6a842b1f669d976472cc52e371 |
| 37223 | 222 |  0 |  2f861ba9e11c7e15810214b1d17c4802 |  222 |    1 |  2f861ba9e11c7e15810214b1d17c4802 |
|  9223 | 222 |  1 |  497d0b20f66cebdedc7935e3ffd46efa |  222 |    5 |  497d0b20f66cebdedc7935e3ffd46efa |
|  8223 | 222 |  3 |  532923f11ac97d3e7cb0130315b067dc |  222 |    1 |  532923f11ac97d3e7cb0130315b067dc |
| 17223 | 222 | 10 |  5e38d982aecea3f5fa249828e8f1548a |  222 |    1 |  5e38d982aecea3f5fa249828e8f1548a |
| 15223 | 222 |  7 |  7d4a769562e3528950e2d1aebdfb0550 |  222 |    2 |  7d4a769562e3528950e2d1aebdfb0550 |
|  2223 | 222 |  9 |  934b535800b1cba8f96a5d72f72f1611 |  222 |    2 |  934b535800b1cba8f96a5d72f72f1611 |
|   223 | 222 |  5 |  bcbe3365e6ac95ea2c0343a2395834dd |  222 |    7 |  bcbe3365e6ac95ea2c0343a2395834dd |
+-------+-----+----+----------------------------------+------+------+----------------------------------+
10  rows in set (0.13 sec)

mysql> SELECT SQL_NO_CACHE COUNT(*) as  total FROM (SELECT a FROM tab1 LEFT OUTER JOIN tab2 USING (a) WHERE  tab1.b=222 AND a != 5223 GROUP BY tab1.c) as zzz;
+-------+
|  total |
+-------+
|    11 |
+-------+
1 row in set (0.10  sec)

W drugim przypadku, ze względu na GROUP BY, konieczne jest wykonanie COUNT(*) na podzapytaniu, dopiero wtedy w wyniku uzyskujemy ilość rekordów. Jak widać, łącznie zajęło to 0,23 sekundy, czyli prawie dwukrotnie więcej niż pierwsze rozwiązanie.
Sprawdźmy teraz, jak sprawa wygląda w przypadku prostszego zapytania:

mysql>  SELECT SQL_NO_CACHE * FROM tab1 WHERE b=222 LIMIT 10;
+--------+-----+---+----------------------------------+
|  a      | b   | c | d                                |
+--------+-----+---+----------------------------------+
|   37223 | 222 | 0 | 2f861ba9e11c7e15810214b1d17c4802 |
|  50223 | 222 |  0 | 9407c90c49bf04f8b59677647ef56522 |
|  52223 | 222 | 0 |  cfe9d26390606405f1a2d2095a8fea96 |
| 106223 | 222 | 0 |  93afab528f766cb76bb911671b0f98c4 |
| 110223 | 222 | 0 |  477d4b7918c7fd5f544af3192976fb24 |
| 112223 | 222 | 0 |  7e2dc464248a306c5e43ca48e9d9488b |
| 129223 | 222 | 0 |  c23a90854bea7fe90a4a80e2aa34cb54 |
| 135223 | 222 | 0 |  95d7952c87bf12e9ba419498c09f9b27 |
| 158223 | 222 | 0 |  3dc9243a4fa705a75fbb685991d04c7a |
| 181223 | 222 | 0 |  a3d41682b4bbd484eeef0bb40e3e07d4 |
+--------+-----+---+----------------------------------+
10  rows in set (0.00 sec)

mysql> SELECT SQL_NO_CACHE COUNT(*)  FROM tab1 WHERE b=222;
+----------+
| COUNT(*) |
+----------+
|     10000 |
+----------+
1 row in set (0.00 sec)

Jak widać, oba zapytania wykonują się poniżej 0,01 sekundy. W przypadku użycia SQL_CALC_FOUND_ROWS wygląda to następująco:

mysql>  SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS * FROM tab1 WHERE b=222 LIMIT  10;
+--------+-----+---+----------------------------------+
|  a      | b   | c | d                                |
+--------+-----+---+----------------------------------+
|   37223 | 222 | 0 | 2f861ba9e11c7e15810214b1d17c4802 |
|  50223 | 222 |  0 | 9407c90c49bf04f8b59677647ef56522 |
|  52223 | 222 | 0 |  cfe9d26390606405f1a2d2095a8fea96 |
| 106223 | 222 | 0 |  93afab528f766cb76bb911671b0f98c4 |
| 110223 | 222 | 0 |  477d4b7918c7fd5f544af3192976fb24 |
| 112223 | 222 | 0 |  7e2dc464248a306c5e43ca48e9d9488b |
| 129223 | 222 | 0 |  c23a90854bea7fe90a4a80e2aa34cb54 |
| 135223 | 222 | 0 |  95d7952c87bf12e9ba419498c09f9b27 |
| 158223 | 222 | 0 |  3dc9243a4fa705a75fbb685991d04c7a |
| 181223 | 222 | 0 |  a3d41682b4bbd484eeef0bb40e3e07d4 |
+--------+-----+---+----------------------------------+
10  rows in set (0.02 sec)

mysql> SELECT FOUND_ROWS();
+--------------+
|  FOUND_ROWS() |
+--------------+
|        10000 |
+--------------+
1  row in set (0.00 sec)

Oba zapytania trwają łącznie 0,02 sekundy. Różnica jest, jak widać, znaczna.

Jaki z powyższego wniosek? Każde zapytanie trzeba sprawdzić na konkretnej bazie, w konkretnej konfiguracji i z konkretnym zestawem danych. Nie da się jednoznacznie określić, czy szybsze jest rozwiązanie z SQL_CALC_FOUND_ROWS czy też lepiej jest jednak wykonać osobno SELECT, a osobno SELECT COUNT(*). Teoretycznie, na podstawie powyższych wyników można by stwierdzić, że w przypadku bardziej skomplikowanych zapytań (JOIN, GROUP BY itp.) szybsze jest zastosowanie SQL_CALC_FOUND_ROWS, a w przypadku zapytań prostych, gez grupowań, JOINów i innych takich atrakcji – SELECT COUNT(*). Tylko że to wcale nie musi być prawda. Może się okazać, że na inaczej skonfigurowanym serwerze bazodanowym wyniki będą zupełnie inne (i zasadniczo tak jest – jeśli pogooglamy za tym problemem, natkniemy się na kompletnie sprzeczne stwierdzenia).

Jedyne, co mogę napisać, jest to, że podczas podejmowania decyzji co do struktury bazy, rodzaju zapytania itp. podstawą jest benchmarkowanie.  Nawet zwykłe odpalenie różnych zapytań po tysiąc razy w pętli  i sprawdzenie jak długo się ta pętla wykonuje, da już jakieś pojęcie o tym, które z nich jest szybsze. Profiling i polecenie EXPLAIN pozwolą zrozumieć co się w zapytaniu dzieje i czym jedno różni się od drugiego. Niestety, ku rozpaczy części użytkowników, którzy chcieliby jednoznacznej odpowiedzi, w przypadku MySQL bardzo często jedyną słuszną odpowiedzią na pytanie „co jest lepsze”, jest – to zależy, sprawdź to sam, na swoim serwerze.