Dois-je COUNT(*) ou pas?
Je sais que c'est généralement une mauvaise idée de faire des requêtes comme:
SELECT * FROM `group_relations`
Mais quand je veux juste le compte, devrais-je aller pour cette requête car cela permet à la table de changer mais donne toujours les mêmes résultats.
SELECT COUNT(*) FROM `group_relations`
Ou le plus specfic
SELECT COUNT(`group_id`) FROM `group_relations`
J'ai le sentiment que ce dernier pourrait potentiellement être plus rapide, mais y a-t-il d'autres choses à considérer?
Update : j'utilise InnoDB dans ce cas, désolé de ne pas être plus précis.
14 réponses
Si la colonne en question N'est pas NULL, vos deux requêtes sont équivalentes. Lorsque group_id contient des valeurs nulles,
select count(*)
Comptera toutes les lignes, alors que
select count(group_id)
Ne comptera que les lignes où group_id n'est pas null.
En outre, certains systèmes de base de données, comme MySQL, utilisent une optimisation lorsque vous demandez count (*), ce qui rend ces requêtes un peu plus rapides que celles spécifiques.
Personnellement, quand je compte, je fais count (*) pour être du bon côté avec le NULL.
Si je m'en souviens bien, dans MYSQL COUNT (*) compte toutes les lignes, alors que COUNT (column_name) ne compte que les lignes qui ont une valeur non nulle dans la colonne donnée.
COUNT ( * ) compte toutes les lignes tandis que COUNT (column_name) ne compte que les lignes sans valeurs NULL dans la colonne spécifiée.
Important à noter dans MySQL:
COUNT () est très rapide sur les tables MyISAM pour les colonnes * ou not-null, puisque le nombre de lignes est mis en cache. InnoDB n'a pas de mise en cache de nombre de lignes, donc il n'y a pas de différence de performance pour COUNT(*) ou COUNT(column_name), que la colonne puisse être nulle ou non. Vous pouvez en savoir plus sur les différences sur ce post sur MySQL les performances de blog.
Si vous essayez SELECT COUNT(1) FROM
group_relations, ce sera un peu plus rapide car il n'essaiera pas de récupérer des informations de vos colonnes.
Edit: je viens de faire quelques recherches et j'ai découvert que cela ne se produit que dans une base de données. Dans sqlserver, il est identique d'utiliser 1 ou *, mais sur oracle, il est plus rapide d'utiliser 1.
Apparemment, il n'y a pas de différence entre eux dans mysql, comme sqlserver l'analyseur semble changer la requête en select (1). Désolé si je vous ai induit en erreur d'une manière ou d'une autre.
J'étais curieux à ce sujet moi-même. C'est bien de lire la documentation et les réponses théoriques, mais j'aime équilibrer ceux-ci avec des preuves empiriques.
J'ai une table MySQL (InnoDB) qui contient 5 607 997 enregistrements. La table est dans mon propre bac à sable privé, donc je sais que le contenu est statique et que personne d'autre n'utilise le serveur. Je pense que cela supprime efficacement tous les effets extérieurs sur les performances. J'ai une table avec un champ de clé primaire auto_increment (Id) que je sais ne sera jamais null que je vais utiliser pour mon test de clause where (où Id N'est pas NULL).
Le seul autre problème possible que je vois dans l'exécution des tests est le cache. La première fois qu'une requête est exécutée sera toujours plus lent que les requêtes qui utilisent les mêmes indices. Je vais me référer à cela ci-dessous comme l'appel d'ensemencement de cache. Juste pour le mélanger un peu, je l'ai couru avec une clause where que je sais toujours évaluer à true indépendamment de toutes les données (TRUE = TRUE).
Cela dit voici mes résultats:
Type de requête
| w/o WHERE | where id is not null | where true=true
COUNT ()
| 9 min 30.13 sec ++ | 6 min 16.68 sec ++ | 2 min 21.80 sec ++
| 6 min 13.34 sec | 1 min 36.02 sec | 2 min 0.11 sec
| 6 min 10.06 se | 1 min 33.47 sec | 1 min 50.54 sec
Nombre (Id)
| 5 min 59.87 sec | 1 min 34.47 sec | 2 min 3.96 sec
| 5 min 44.95 sec | 1 min 13.09 sec | 2 min 6.48 sec
Compte (1)
| 6 min 49.64 sec | 2 min 0.80 sec | 2 min 11.64 sec
| 6 min 31.64 sec | 1 min 41.19 sec | 1 min 43.51 sec
++Ceci est considéré comme l'appel d'ensemencement de cache. Il devrait être plus lent que le reste.
, je dirais que les résultats parlent d'eux-mêmes. COUNT (Id) exclut généralement les autres. L'ajout d'une clause where diminue considérablement le temps d'accès, même si c'est une clause vous savez évaluer la valeur true. Le sweet spot semble être COUNT (Id)... Où Id N'est pas NULL.
J'aimerais voir d'autres les résultats des peuples, peut-être avec des tables plus petites ou avec des clauses where contre des champs différents que le champ que vous comptez. Je suis sûr qu'il y a d'autres variations que je n'ai pas prises en compte.
Rechercher Des Alternatives
Comme vous l'avez vu, lorsque les tables grossissent, les requêtes COUNT
sont lentes. Je pense que le plus important est de considérer la nature du problème que vous essayez de résoudre. Par exemple, de nombreux développeurs utilisent des requêtes COUNT
lors de la génération de pagination pour de grands ensembles d'enregistrements afin de déterminer le nombre total de pages dans le jeu de résultats.
Sachant que les requêtes COUNT
vont ralentir, vous pouvez envisager une autre façon d'afficher les contrôles de pagination qui vous permet simplement de contourner la requête lente. La pagination de Google est un excellent exemple.
Dénormaliser
Si vous devez absolument connaître le nombre d'enregistrements correspondant à un nombre spécifique, considérez la technique classique de dénormalisation des données. Au lieu de compter le nombre de lignes au moment de la recherche, envisagez d'incrémenter un compteur lors de l'insertion d'un enregistrement et de décrémenter ce compteur lors de la suppression d'un enregistrement.
Si vous décidez de le faire, envisagez d'utiliser idempotent, transactional opérations permettant de synchroniser ces valeurs dénormalisées.
BEGIN TRANSACTION;
INSERT INTO `group_relations` (`group_id`) VALUES (1);
UPDATE `group_relations_count` SET `count` = `count` + 1;
COMMIT;
Vous pouvez également utiliser des déclencheurs de base de données si votre SGBDR les prend en charge.
Selon votre architecture, il peut être judicieux d'utiliser une couche de mise en cache comme memcached pour stocker, incrémenter et décrémenter la valeur dénormalisée, et simplement passer à la requête de comptage lent lorsque la clé de cache est manquante. Cela peut réduire la contention d'écriture globale si vous avez des données très volatiles, bien que dans des cas comme celui-ci, vous vous voulez envisager des solutions à l'effet dog-pile .
Les tables ISAM MySQL devraient avoir une optimisation pour COUNT (*), en ignorant l'analyse complète de la table.
Un astérisque dans COUNT n'a pas d'incidence avec un astérisque pour sélectionner tous les champs de la table. Il est tout à fait inutile de dire que COUNT ( * ) est plus lent que COUNT(field)
J'imagine que select COUNT (*) est plus rapide que select COUNT (field). Si le SGBDR détecte que vous spécifiez " * " sur COUNT au lieu de field, il n'a pas besoin d'évaluer quoi que ce soit pour incrémenter count. Alors que si vous spécifiez field on COUNT, le SGBDR évaluera toujours si votre champ est null ou non pour le Compter.
Mais si votre champ est nullable, spécifiez le champ dans COUNT.
Compter ( * ) faits et mythes:
MYTH : "InnoDB ne gère pas bien les requêtes count (*)":
La plupart des requêtes count(*) sont exécutées de la même manière par tous les moteurs de stockage si vous avez une clause WHERE, sinon InnoDB devra effectuer une analyse complète de la table.
Fait : InnoDB n'optimise pas les requêtes count(*) sans la clause where
, Il est préférable de compter par une colonne indexée comme une clé primaire.
SELECT COUNT(`group_id`) FROM `group_relations`
Cela devrait dépendre de ce que vous essayez réellement d'accomplir comme Sebastian l'a déjà dit, c'est-à-dire que vos intentions soient claires! Si vous Êtes juste en comptant les lignes, alors allez pour le compte(*), ou en comptant une seule colonne allez pour le compte(colonne).
Il pourrait être utile de vérifier votre fournisseur de base de données aussi. À L'époque où J'utilisais Informix, il avait une optimisation pour COUNT (*) qui avait un coût d'exécution de plan de requête de 1 par rapport au comptage de colonnes simples ou mutliple qui résulterait dans un chiffre supérieur
Si vous essayez SELECT COUNT (1) de group_relations, ce sera un peu plus rapide car il n'essaiera pas de récupérer les informations de vos colonnes.
COUNT (1) était plus rapide que COUNT ( * ), mais ce N'est plus vrai, car les SGBD modernes sont assez intelligents pour savoir que vous ne voulez pas connaître les colonnes
Le conseil que J'ai reçu de MySQL à propos de ce genre de choses est que, en général, essayer d'optimiser une requête basée sur des astuces comme celle-ci peut être une malédiction à long terme. Il y a des exemples sur L'histoire de MySQL où la technique de haute performance de quelqu'un qui repose sur le fonctionnement de l'optimiseur finit par être le goulot d'étranglement dans la prochaine version.
Écrivez la requête qui répond à la question que vous posez-si vous voulez un compte de toutes les lignes, utilisez COUNT (*). Si vous voulez un nombre de colonnes non null, utilisez COUNT (col) où col N'est pas NULL. Indexez correctement, et laissez l'optimisation à l'optimiseur. Essayer de faire vos propres optimisations au niveau de la requête peut parfois rendre l'optimiseur intégré moins efficace.
Cela dit, il y a des choses que vous pouvez faire dans une requête pour faciliter l'accélération de L'optimiseur, mais je ne crois pas que COUNT soit l'un d'entre eux.
Edit: les statistiques dans la réponse ci-dessus sont intéressantes, cependant. Je ne suis pas sûr s'il y a réellement quelque chose à le travail de l'optimiseur dans ce cas. Je parle juste des optimisations au niveau des requêtes en général.
Je sais que c'est généralement une mauvaise idée de faire requêtes comme ceci:
SELECT * FROM `group_relations`
Mais quand je veux juste le compte, devrait Je vais pour cette requête puisque cela permet la table à changer mais donne toujours les mêmes résultats.
SELECT COUNT(*) FROM `group_relations`
Comme votre question l'indique, la raison pour laquelle SELECT *
est mal avisé est que les modifications apportées à la table pourraient nécessiter des modifications dans votre code. Cela ne s'applique pas à COUNT(*)
. Il est assez rare de vouloir le comportement spécialisé que SELECT COUNT('group_id')
Vous donne-généralement vous voulez connaître le nombre d'enregistrements. C'est à quoi sert COUNT(*)
, alors utilisez-le.