Какой самый быстрый? SELECT SQL_CALC_FOUND_ROWS FROM `table` или SELECT COUNT (*)


176

Когда вы ограничиваете количество строк, возвращаемых 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;  

Какой метод самый лучший / самый быстрый?

Ответы:


120

Это зависит. См. Блог MySQL Performance Blog на эту тему: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Просто краткое резюме: Питер говорит, что это зависит от ваших показателей и других факторов. Многие комментарии к посту, кажется, говорят, что SQL_CALC_FOUND_ROWS почти всегда медленнее - иногда до 10 раз медленнее - чем выполнение двух запросов.


27
Я могу подтвердить это - я только что обновил запрос с 4 объединениями в базе данных на 168 000 строк. Выбор только первых 100 строк с использованием SQL_CALC_FOUND_ROWSзанял более 20 секунд; использование отдельного COUNT(*)запроса заняло менее 5 секунд (для обоих запросов count + result).
Сэм Дюфель

9
Очень интересные выводы. Поскольку документация в MySQL явно говорит о том , что SQL_CALC_FOUND_ROWSбудет быстрее, интересно , в каких ситуациях (если таковые имеются) , то на самом деле это быстрее!
svidgen

12
Старая тема, но для тех, кому еще интересно! Только что закончил проверку INNODB из 10 проверок. Я могу сказать, что это 26 (2 запроса) против 9,2 (1 запрос). ВЫБЕРИТЕ SQL_CALC_FOUND_ROWS 'c_id', 'c_type' tblC.type А.С., 'd_id' tblD.id А.С., 'd_extype' tblD.extype А.С., 'y_id' tblY.id А.С., tblY.ydt А.С. y_ydt ОТ tblA, tblB, tblC, tblD, tblY ГДЕ tblA.b = tblC.id AND tblA.c = tblB.id AND tblA.d = tblD.id AND tblA.y = tblY.id
Аль По

4
Я только что провел этот эксперимент, и SQLC_CALC_FOUND_ROWS был намного быстрее, чем два запроса. Теперь моя основная таблица составляет всего 65 КБ и два объединения из нескольких сотен, но основной запрос занимает 0,18 секунды с или без SQLC_CALC_FOUND_ROWS, но когда я запустил второй запрос с COUNT ( id), потребовалось только 0,25.
Transilvlad

1
В дополнение к возможным проблемам производительности, FOUND_ROWS()учтите , что это устарело в MySQL 8.0.17. Смотрите также ответ @ madhur-bhaiya.
arueckauer

19

При выборе «наилучшего» подхода, более важным фактором, чем скорость, может быть ремонтопригодность и правильность вашего кода. Если так, то SQL_CALC_FOUND_ROWS предпочтительнее, потому что вам нужно поддерживать только один запрос. Использование одного запроса полностью исключает тонкую разницу между запросами main и count, что может привести к неточному подсчету.


11
Это зависит от вашей настройки. Если вы используете какой-либо ORM или построитель запросов, очень легко использовать одинаковые критерии для обоих запросов, поменять местами поля выбора для счетчика и сбросить ограничение. Вы никогда не должны выписывать критерии дважды.
mpen

Я хотел бы отметить, что я предпочел бы поддерживать код, используя два простых, довольно простых и понятных SQL-запроса, чем тот, который использует проприетарную функцию MySQL - что стоит отметить, не рекомендуется в более новых версиях MySQL.
Томасруттер

15

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, так и с точки зрения использования индекса), а не по номеру записи. Обсуждения становятся все более бесконечными, упорядоченные по дате (опять же, позволяя использовать индекс), а не по нумерации страниц. И так далее.


Как выполнить эти два выбора как атомарную операцию? Что если кто-то вставит строку перед запросом SELECT COUNT (*)? Спасибо.
Дом

@Dom, если у вас есть MySQL8 +, вы можете выполнить оба запроса в одном запросе, используя функции Window; но это не будет оптимальным решением, так как индексы не будут использоваться должным образом. Другой вариант - заключить эти два запроса в LOCK TABLES <tablename>и UNLOCK TABLES. Третий вариант и (лучший ИМХО) заключается в переосмыслении нумерации страниц. Пожалуйста, прочитайте: mariadb.com/kb/en/library/pagination-optimization
Мадхур Бхайя,

14

Согласно следующей статье: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Если у вас есть INDEX в вашем предложении where (если id индексируется в вашем случае), то лучше не использовать SQL_CALC_FOUND_ROWS и использовать вместо него 2 запроса, но если у вас нет индекса для того, что вы поместили в ваше предложение where (id в вашем случае), тогда использование SQL_CALC_FOUND_ROWS более эффективно.


8

ИМХО, причина, почему 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 »!


6

Удаление некоторого ненужного 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'

3

Есть и другие варианты для сравнения:

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` = ?
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.