Что быстрее? 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' = ?