Що найшвидше? ВИБІРТЕ SQL_CALC_FOUND_ROWS З `таблиці 'або 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: 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 об’єднаннями в базі даних 168000 рядків. Вибір лише перших 100 рядків із SQL_CALC_FOUND_ROWSзайняттям понад 20 секунд; використання окремого COUNT(*)запиту займало менше 5 секунд (для обох запитів підрахунку + результатів).
Сем Дюфель

9
Дуже цікаві висновки. Оскільки документація MySQL явно говорить про те, що SQL_CALC_FOUND_ROWSце буде швидше, мені цікаво, в яких ситуаціях (якщо такі є) насправді це швидше!
svidgen

12
стара тема, але для тих, хто ще цікавий! Щойно закінчив перевірку на INNODB з 10 перевірок, я можу сказати, що це 26 (2 запит) проти 9.2 (1 запит). '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 І tblA.c = tblB.id І tblA.d = tblD.id І tblA.y = tblY.id
Al Po

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 є кращим, оскільки вам потрібно підтримувати лише один запит. Використання одного запиту повністю виключає можливість тонкої різниці між основним і підрахунковим запитами, що може призвести до неточної КУНТУ.


11
Це залежить від вашого настрою. Якщо ви використовуєте якийсь ORM або конструктор запитів, дуже просто використовувати одне і те ж, де критерії для обох запитів, поміняти місцями вибрані поля для підрахунку та скинути ліміт. Ніколи не слід виписувати критерії двічі.
mpen

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

15

MySQL почав SQL_CALC_FOUND_ROWSзнімати функціональність з версії 8.0.17 і далі.

Отже, завжди бажано розглянути можливість виконання вашого запиту за допомогою LIMIT, а потім другий запит з COUNT(*)і без, LIMITщоб визначити, чи є додаткові рядки.

З документів :

Модифікатор запитів SQL_CALC_FOUND_ROWS та супровідна функція FOUND_ROWS () застаріли як MySQL 8.0.17 і будуть видалені в майбутній версії 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 ( ) може використовувати оптимізації, які неможливо виконати при пошуку всього набору результатів (наприклад, fileort можна пропустити на 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. Третій варіант і (найкращий IMHO) - переосмислити пагинацію. Прочитайте: 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/

Якщо у вас є пункт « ІНДЕКС» у пункті «де» (якщо у вашому випадку індексовано ідентифікатор), то краще не використовувати SQL_CALC_FOUND_ROWS та використовувати 2 запити замість цього, але якщо у вас немає індексу того, що ви ставите в пункті де (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 ), той факт, що ЕКСПЛІАТ використовує, чи ні, таблиця темпорай, повинна бути хорошою базою для того, щоб знати, чи буде SCFR швидше чи ні.

Але, як я додав ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/#comment-8166482 ), результат дійсно залежить від конкретного випадку. Для конкретного пагинатора можна дійти висновку, що "для 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.