Занимаясь оптимизацией клиентских запросов, я довольно часто вижу запросы, в которых используется SQL_CALC_FOUND_ROWS. Многие думают, что данная конструкция намного быстрее, чем выполнение двух запросов: получение данных SELECT и подсчет количества записей при помощи COUNT. Попробуем разобраться что к чему.
Создадим следующую таблицу:
CREATE TABLE `count_test` ( `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
Заполняем её случайными данными:
mysql_connect("127.0.0.1", "root");
mysql_select_db("test");
$num = 10000000;
for ($i = 0; $i < $num; $i++) {
$b = $i % 1000;
$sql = "INSERT INTO `count_test`
SET `b` = {$b},
`c` = ROUND(RAND() * 10),
`d` = MD5({$i})
";
mysql_query($sql);
}
Сначала попытаемся выполнить запрос, используя проиндексированную колонку b в выражении WHERE:
SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS * FROM `count_test` WHERE `b` = 555 ORDER BY `c` LIMIT 5;
В итоге, для каждого значения b запрос выполнялся 20-100 секунд в первый раз и 2-5 секунд — после кэширования. Такая разность объясняется издержками ввода-вывода, которые требуются MySQL для обработки 10000 строк без конструкции LIMIT.
А что будет, если запрос разбить на два отдельных:
SELECT SQL_NO_CACHE * FROM `count_test` WHERE `b` = 666 ORDER BY c LIMIT 5;
Первый раз запрос выполняется 0.01-0.11 сек, 0.00-0.02 сек — в последующие. Теперь посмотрим, сколько будет работать COUNT:
SELECT SQL_NO_CACHE COUNT(*) FROM `count_test` WHERE `b` = 666;
Результат ошеломляющий — 0.00-0.04 сек. Получается, что общее время выполнения запросов SELECT и COUNT лежит в промежутке от 0.00 сек до 0.15 сек, что намного меньше времени выполнения исходного запроса. Что на это скажет EXPLAIN:
EXPLAIN SELECT SQL_CALC_FOUND_ROWS * FROM `count_test` WHERE `b` = 999 ORDER BY `c` LIMIT 5;
Результат:
Запрос с использованием COUNT:
EXPLAIN SELECT SQL_NO_CACHE COUNT(*) FROM `count_test` WHERE `b` = 666;
Результат:
По результатам запросов видно, что SQL_CALC_FOUND_ROWS заставляет MySQL обрабатывать все данные в таблице, даже если они не нужны в результате (мы запросили всего пять LIMIT 5), а при использовании COUNT применяется индекс, из-за чего такой запрос выполняется гораздо быстрее.
Проведем те же тесты на таблице с индексом на колонке b:
| Тест | Без индекса Full-scan, сек |
С индексом на колонке b Filesort, сек |
|---|---|---|
| SQL_CALC_FOUND_ROWS | 7.0 | 7.0 + 7.0 |
| SELECT + COUNT | 1.8 | 1.8 + 0.05 |
Вывод
Если у нас есть подходящие индексы для WHERE и ORDER, то использование двух отдельных запросов вполне может быть намного быстрее чем один с SQL_CALC_FOUND_ROWS.
Ссылки
Источник: https://www.kobzarev.com/programming/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/