web-dev-qa-db-fra.com

Lequel est le plus rapide? SELECT SQL_CALC_FOUND_ROWS FROM `table` ou SELECT COUNT (*)

Lorsque vous limitez le nombre de lignes à renvoyer par une requête SQL, généralement utilisé dans la pagination, deux méthodes permettent de déterminer le nombre total d'enregistrements:

Méthode 1

Incluez l'option SQL_CALC_FOUND_ROWS Dans l'original SELECT, puis obtenez le nombre total de lignes en exécutant SELECT FOUND_ROWS():

SELECT SQL_CALC_FOUND_ROWS * FROM table WHERE id > 100 LIMIT 10;
SELECT FOUND_ROWS();  

Méthode 2

Exécutez la requête normalement, puis obtenez le nombre total de lignes en exécutant SELECT COUNT(*).

SELECT * FROM table WHERE id > 100 LIMIT 10;
SELECT COUNT(*) FROM table WHERE id > 100;  

Quelle méthode est la meilleure/la plus rapide?

165
Jrgns

Ça dépend. Consultez l'article de MySQL Performance sur ce sujet: http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-to-sql_calc_found_rows/

Juste un bref résumé: Peter dit que cela dépend de vos index et d’autres facteurs. De nombreux commentaires dans l'article semblent dire que SQL_CALC_FOUND_ROWS est presque toujours plus lent - parfois jusqu'à 10 fois plus lent - que l'exécution de deux requêtes.

112
nathan

Lors du choix de la "meilleure" approche, une considération plus importante que la rapidité peut être la maintenabilité et l'exactitude de votre code. Si tel est le cas, SQL_CALC_FOUND_ROWS est préférable car vous ne devez gérer qu'une seule requête. L'utilisation d'une seule requête exclut complètement la possibilité d'une différence subtile entre la requête principale et la requête de comptage, ce qui peut conduire à un COUNT inexact.

20
Jeff Clemens

Selon l'article suivant: https://www.percona.com/blog/2007/08/28/to-sql_calc_found_rows-or-not-or-sot-sql_calc_found_rows/

Si vous avez un [~ # ~] index [~ # ~] sur votre clause where (si id est indexé dans votre cas), il est préférable de ne pas utiliser SQL_CALC_FOUND_ROWS et utilisez 2 requêtes à la place, mais si vous n'avez pas d'index sur ce que vous mettez dans votre clause where (id dans votre cas), utilisez ensuite SQL_CALC_FOUND_ROWS est plus efficace.

13
patapouf_ai

IMHO, la raison pour laquelle 2 requêtes

SELECT * FROM count_test WHERE b = 666 ORDER BY c LIMIT 5;
SELECT count(*) FROM count_test WHERE b = 666;

sont plus rapides que d'utiliser SQL_CALC_FOUND_ROWS

SELECT SQL_CALC_FOUND_ROWS * FROM count_test WHERE b = 555 ORDER BY c LIMIT 5;

doit être considéré comme un cas particulier.

Cela dépend en fait de la sélectivité de la clause WHERE par rapport à la sélectivité de l'équivalent implicite de ORDER + LIMIT.

Comme Arvids l'a dit dans un commentaire ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-tors-l-sql_calc_found_rows/#comment-1174394 ), le fait que L'utilisation EXPLAIN, ou non, d'une table temporaire, devrait être une bonne base pour savoir si le SCFR sera plus rapide ou pas.

Mais, comme je l'ai ajouté ( http://www.mysqlperformanceblog.com/2007/08/28/to-sql_calc_found_rows-or-not-tors-l-calf_calc_found_rows/#comment-8166482 ), le résultat est vraiment , dépend vraiment du cas. Pour un paginateur particulier, vous pouvez arriver à la conclusion que “pour les 3 premières pages, utilisez 2 requêtes; pour les pages suivantes, utilisez un SCFR ”!

8

Supprimer quelques SQL inutiles puis COUNT(*) sera plus rapide que SQL_CALC_FOUND_ROWS. Exemple:

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

Puis compte sans partie inutile:

SELECT COUNT(*)
FROM Person
JOIN Job ON Job.Id = Person.Job_Id
WHERE Job.Name = 'WEB Developer'
6
Jessé Catrinck

MySQL a commencé à rendre obsolète la fonctionnalité SQL_CALC_FOUND_ROWS à partir de la version 8.0.17.

Donc, il est toujours préférable de considérer l'exécution de votre requête avec LIMIT, puis une seconde requête avec COUNT(*) et sans LIMIT pour déterminer s’il existe des lignes supplémentaires.

De docs :

Le modificateur de requête SQL_CALC_FOUND_ROWS et la fonction FOUND_ROWS () qui l'accompagne sont obsolètes à partir de MySQL 8.0.17 et seront supprimés dans une future version de MySQL.

COUNT (*) est soumis à certaines optimisations. SQL_CALC_FOUND_ROWS entraîne la désactivation de certaines optimisations.

Utilisez plutôt ces requêtes:

SELECT * FROM tbl_name WHERE id > 100 LIMIT 10;
SELECT COUNT(*) WHERE id > 100;

En outre, il a été observé que SQL_CALC_FOUND_ROWS Présentait généralement plus de problèmes, comme expliqué dans MySQL WL # 12615 :

SQL_CALC_FOUND_ROWS a un certain nombre de problèmes. Tout d'abord, c'est lent. Fréquemment, il serait moins coûteux d’exécuter la requête avec LIMIT, puis un SELECT COUNT distinct () pour la même requête, car COUNT () peut utiliser des optimisations qui ne peuvent pas être effectuées lorsque la recherche de l'ensemble des résultats (par exemple, le fichier peut être ignoré pour COUNT (*), alors qu'avec CALC_FOUND_ROWS, nous devons désactiver certaines optimisations du fichier afin de garantir le bon résultat)

Plus important encore, sa sémantique est très floue dans un certain nombre de situations. En particulier, lorsqu'une requête comporte plusieurs blocs de requête (par exemple, avec UNION), il est tout simplement impossible de calculer le nombre de lignes "aurait dû être" en même temps que la production d'une requête valide. Au fur et à mesure que l'exécutateur itérateur avance vers ce type de requêtes, il est réellement difficile d'essayer de conserver la même sémantique. De plus, s'il y a plusieurs LIMITs dans la requête (par exemple, pour les tables dérivées), il n'est pas nécessairement indiqué à laquelle d'entre eux SQL_CALC_FOUND_ROWS doit faire référence. Ainsi, de telles requêtes non triviales auront nécessairement une sémantique différente dans l'exécutant de l'itérateur par rapport à ce qu'elles avaient auparavant.

Enfin, la plupart des cas d'utilisation pour lesquels SQL_CALC_FOUND_ROWS semble utile devraient simplement être résolus par d'autres mécanismes que LIMIT/OFFSET. Par exemple, un annuaire téléphonique doit être paginé par lettre (à la fois en termes d’UX et d’utilisation de l’index), et non par un numéro d’enregistrement. Les discussions sont de plus en plus infinies-défilement ordonné par date (permettant à nouveau l'utilisation de l'index), pas par paginé par numéro de poste. Etc.

5
Madhur Bhaiya

Il existe d’autres options à évaluer:

1.) Une fonction window retournera directement la taille réelle (testée dans MariaDB):

SELECT 
  `mytable`.*,
  COUNT(*) OVER() AS `total_count`
FROM `mytable`
ORDER BY `mycol`
LIMIT 10, 20

2.) En dehors de la boîte, la plupart du temps, les utilisateurs n'ont pas besoin de connaître le [~ # ~] exact [~ # ~] taille de la table, une approximation suffit souvent.

SELECT `TABLE_ROWS` AS `rows_approx`
FROM `INFORMATION_SCHEMA`.`TABLES`
WHERE `TABLE_SCHEMA` = DATABASE()
  AND `TABLE_TYPE` = "BASE TABLE"
  AND `TABLE_NAME` = ?
1
Code4R7