Что быстрее? SELECT SQL_CALC_FOUND_ROWS FROM `table` или SELECT COUNT (*)
Когда вы ограничиваете количество строк, возвращаемых SQL-запросом, обычно используемым в подкачке, существует два метода для определения общего количества записей:
Способ 1
Включите опцию SQL_CALC_FOUND_ROWS
в исходном SELECT
, а затем получите общее количество строк, запустив SELECT FOUND_ROWS()
:
SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();
Способ 2
Запустите запрос в обычном режиме, а затем получите общее количество строк, запустив SELECT COUNT(*)
SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;
Какой метод является лучшим/быстрым?
Ответы
Ответ 1
Это зависит. Смотрите сообщение о производительности MySQL Performance на эту тему: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
Просто краткое резюме: Питер говорит, что это зависит от ваших индексов и других факторов. Многие комментарии к сообщению, похоже, говорят, что SQL_CALC_FOUND_ROWS почти всегда медленнее - иногда до 10 раз медленнее - чем запуск двух запросов.
Ответ 2
При выборе "лучшего" подхода более важным соображением, чем скорость, может быть удобство и правильность вашего кода. Если это так, то SQL_CALC_FOUND_ROWS предпочтительнее, поскольку вам нужно поддерживать только один запрос. Использование одного запроса полностью исключает возможность тонкой разницы между основными и счетными запросами, что может привести к неточному COUNT.
Ответ 3
В соответствии со следующей статьей: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/
Если у вас есть ИНДЕКС в вашем предложении where (если идентификатор проиндексирован в вашем случае), то лучше не использовать SQL_CALC_FOUND_ROWS и вместо этого использовать 2 запроса, но если у вас нет индекс того, что вы помещаете в свое предложение where (id в вашем случае), затем использует SQL_CALC_FOUND_ROWS.
Ответ 4
ИМХО, причина, по которой 2 запроса
SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;
быстрее, чем использование SQL_CALC_FOUND_ROWS
SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;
следует рассматривать как частный случай.
В действительности это зависит от избирательности предложения WHERE по сравнению с селективностью неявного эквивалента ORDER + LIMIT.
Как сказал Арвидис в комментарии (http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-1174394), тот факт, что EXPLAIN использует или нет, временную таблицу, должен быть хорошей базой для того, чтобы знать, SCFR будет быстрее или нет.
Но, как я добавил (http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482), результат действительно, действительно зависит от случая. Для конкретного paginator вы можете прийти к выводу, что "для 3-х первых страниц используйте 2 запроса, для следующих страниц используйте SCFR"!
Ответ 5
Удаление ненужного SQL, а затем COUNT(*)
будет быстрее, чем SQL_CALC_FOUND_ROWS
. Пример:
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
Тогда посчитайте без лишней части:
SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'
Ответ 6
MySQL начала устаревать функциональность SQL_CALC_FOUND_ROWS
с версии 8.0.17 и выше.
Таким образом, всегда предпочтительнее рассмотреть выполнение вашего запроса с помощью LIMIT
, а затем второй запрос с COUNT(*)
и без LIMIT
чтобы определить, есть ли дополнительные строки.
Из документов:
Начиная с MySQL 8.0.17, модификатор запроса SQL_CALC_FOUND_ROWS и сопровождающая его функция FOUND_ROWS() устарели и будут удалены в будущей версии MySQL.
COUNT (*) подлежит определенной оптимизации. SQL_CALC_FOUND_ROWS вызывает отключение некоторых оптимизаций.
Вместо этого используйте эти запросы:
SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;
Кроме того, SQL_CALC_FOUND_ROWS
как было отмечено, имеет больше проблем в целом, как объяснено в MySQL WL # 12615:
SQL_CALC_FOUND_ROWS имеет ряд проблем. Прежде всего, это медленно. Часто было бы дешевле выполнить запрос с LIMIT, а затем с отдельным SELECT COUNT() для того же запроса, поскольку COUNT() может использовать оптимизации, которые невозможно выполнить при поиске всего набора результатов (например, файловой сортировки). можно пропустить для COUNT (*), тогда как в случае CALC_FOUND_ROWS мы должны отключить некоторые оптимизации сортировки файлов, чтобы гарантировать правильный результат)
Что еще более важно, он имеет очень нечеткую семантику в ряде ситуаций. В частности, когда запрос имеет несколько блоков запроса (например, с помощью UNION), просто невозможно рассчитать количество строк, которые могли бы быть, в то же время, что и при создании действительного запроса. По мере того, как исполнитель итераторов продвигается к таким запросам, действительно трудно попытаться сохранить ту же семантику. Кроме того, если в запросе несколько LIMIT (например, для производных таблиц), не обязательно ясно, на какой из них следует ссылаться в SQL_CALC_FOUND_ROWS. Таким образом, такие нетривиальные запросы обязательно получат другую семантику в исполнителе итератора по сравнению с тем, что они имели раньше.
Наконец, большинство случаев использования, где SQL_CALC_FOUND_ROWS может показаться полезным, должны быть просто решены с помощью других механизмов, нежели LIMIT/OFFSET. Например, телефонная книга должна быть разбита на страницы по буквам (как с точки зрения UX, так и с точки зрения использования индекса), а не по номеру записи. Обсуждения становятся все более бесконечными, упорядоченные по дате (опять же, позволяя использовать индекс), а не по страницам. И так далее.
Ответ 7
Есть и другие варианты для сравнения:
1.) Оконная функция будет возвращать фактический размер напрямую (проверено в MariaDB):
SELECT
'mytable'.*,
COUNT(*) OVER() AS 'total_count'
FROM 'mytable'
ORDER BY 'mycol'
LIMIT 10, 20
2. Думая "из коробки", в большинстве случаев пользователям не нужно знать ТОЧНЫЙ размер таблицы, приблизительное значение часто бывает достаточно хорошим.
SELECT 'TABLE_ROWS' AS 'rows_approx'
FROM 'INFORMATION_SCHEMA'.'TABLES'
WHERE 'TABLE_SCHEMA' = DATABASE()
AND 'TABLE_TYPE' = "BASE TABLE"
AND 'TABLE_NAME' = ?