Dois-je compter (*) ou non?

Je sais que c’est généralement une mauvaise idée de faire des requêtes comme celles-ci:

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 précis

 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?

Mise à jour : J’utilise InnoDB dans ce cas, désolé de ne pas être plus précis.

Si la colonne en question n’est pas NULL, vos deux requêtes sont équivalentes. Lorsque group_id contient des valeurs NULL,

 select count(*) 

comptera toutes les lignes, alors que

 select count(group_id) 

ne compte que les lignes où group_id n’est pas nul.

De plus, certains systèmes de bases de données, tels que MySQL, utilisent une optimisation lorsque vous demandez un compte (*), ce qui accélère un peu ces requêtes.

Personnellement, juste en comptant, je compte (*) pour être du bon côté avec les nulls.

Si je m’en souviens bien, dans MYSQL COUNT (*) compte toutes les lignes, alors que COUNT (nom_colonne) ne compte que les lignes qui ont une valeur non-NULL dans la colonne donnée.

COUNT (*) compte toutes les lignes alors que COUNT (nom_colonne) 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, car le nombre de lignes est mis en cache. InnoDB n’a pas de cache de nombre de lignes, il n’y a donc pas de différence de performance pour COUNT (*) ou COUNT (nom_colonne), que la colonne puisse être nulle ou non. Vous pouvez en savoir plus sur les différences sur ce post sur le blog de performance MySQL.

Si vous essayez SELECT COUNT(1) FROM group_relations, il sera un peu plus rapide car il n’essaiera pas de récupérer les informations de vos colonnes.

Edit: Je viens de faire des recherches et découvert que cela ne se produit que dans certains db. Dans sqlserver, il est identique d’utiliser 1 ou *, mais sur oracle, il est plus rapide d’utiliser 1.

http://social.msdn.microsoft.com/forums/en-US/transactsql/thread/9367c580-087a-4fc1-bf88-91a51a4ee018/

Apparemment, il n’y a pas de différence entre eux dans mysql, comme sqlserver, l’parsingur semble changer la requête à sélectionner (1). Désolé si je vous induis en erreur.

J’étais curieux à ce sujet moi-même. Il est bon de lire la documentation et les réponses théoriques, mais j’aime bien équilibrer celles-ci avec des preuves empiriques.

J’ai une table MySQL (InnoDB) qui contient 5 607 997 enregistrements. La table est dans mon sandbox 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 externes sur la performance. J’ai une table avec un champ clé primaire (Id) auto_increment que je sais ne sera jamais nul que je vais utiliser pour mon test de clause where (WHERE Id IS NOT NULL).

Le seul autre problème 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 lente que les requêtes suivantes qui utilisent les mêmes index. Je l’appellerai ci-dessous en tant qu’appel d’ensemencement du cache. Juste pour le mélanger un peu, je l’ai exécuté avec une clause where qui, je le sais, sera toujours évaluée, quelle que soit la donnée (TRUE = TRUE).

Cela dit, voici mes résultats:

QueryType

  | w/o WHERE | where id is not null | where true=true 

COMPTER()

  | 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 

COUNT (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 du cache. On s’attend à ce qu’il soit plus lent que le rest.

Je dirais que les résultats parlent d’eux-mêmes. COUNT (Id) dépasse généralement les autres. L’ajout d’une clause Where diminue considérablement le temps d’access, même si vous savez que cette clause est considérée comme vraie. Le sweet spot semble être COUNT (Id) … WHERE Id n’est pas NULL.

J’aimerais voir les résultats d’autres personnes, peut-être avec des tables plus petites ou avec des clauses where contre des champs différents du champ que vous comptez. Je suis sûr qu’il y a d’autres variations que je n’ai pas sockets en compte.

Rechercher des alternatives

Comme vous l’avez vu, lorsque les tables deviennent volumineuses, les requêtes COUNT sont lentes. Je pense que la chose la plus importante 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 des ensembles d’enregistrements volumineux 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 manière 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 compte 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 faire cela, envisagez d’utiliser des opérations transactionnelles idempotentes pour conserver ces valeurs dénormalisées en synchronisation.

 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 firebase database si votre SGBDR les prend en charge.

Selon votre architecture, il peut être judicieux d’utiliser une couche de mise en cache telle que memcached pour stocker, incrémenter et décrémenter la valeur dénormalisée, et simplement passer à la requête COUNT lente lorsque la clé de cache est manquante. Cela peut réduire la contention globale en écriture si vous avez des données très volatiles, même si dans de tels cas, vous voudrez envisager des solutions à l’effet de chien-stack .

Les tables MySQL ISAM doivent avoir une optimisation pour COUNT (*), en ignorant l’parsing 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 pur de dire que COUNT (*) est plus lent que COUNT (champ)

Je pense que sélectionner COUNT (*) est plus rapide que sélectionner COUNT (champ). Si le SGBDR a détecté que vous spécifiez “*” sur COUNT au lieu du champ, il n’a pas besoin d’évaluer quoi que ce soit pour incrémenter le nombre. Alors que si vous spécifiez un champ sur COUNT, le SGBDR évaluera toujours si votre champ est nul ou non.

Mais si votre champ est nullable, spécifiez le champ dans COUNT.

COUNT (*) faits et mythes:

MYTHE : “InnoDB ne gère pas bien les requêtes (*)”:

La plupart des requêtes de compte (*) 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 parsing complète de la table.

FAIT : InnoDB n’optimise pas les requêtes de comptage (*) sans la clause where

Il est préférable de compter par une colonne indexée telle qu’une clé primaire.

 SELECT COUNT(`group_id`) FROM `group_relations` 

Cela devrait dépendre de ce que vous essayez réellement de réaliser, comme l’a déjà dit Sebastian, c’est-à-dire clarifier vos intentions! Si vous comptez simplement les lignes, optez pour COUNT (*) ou comptez une seule colonne pour COUNT (colonne).

Il peut être utile de consulter également votre fournisseur de firebase database. A l’époque, j’utilisais Informix, il y avait une optimisation pour COUNT (*) qui avait un coût d’exécution de plan de requête de 1 comparé au comptage de colonnes simples ou multiples, ce qui se traduirait par un chiffre plus élevé.

Si vous essayez SELECT COUNT (1) FROM group_relations, il 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, puisque les SGBD modernes sont assez intelligents pour savoir que vous ne voulez pas connaître les colonnes.

Le conseil de MySQL à ce sujet 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 haute performance de quelqu’un qui repose sur le fonctionnement de l’optimiseur finit par devenir le goulot d’étranglement de 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) WHERE col IS NOT NULL. Indexer de manière appropriée et laisser 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’optimisation de l’optimiseur, mais je ne pense pas que COUNT en fait partie.

Edit: Les statistiques dans la réponse ci-dessus sont intéressantes, cependant. Je ne suis pas sûr qu’il y ait réellement quelque chose à faire dans l’optimiseur dans ce cas. Je parle simplement d’optimisations au niveau de la requête en général.

Je sais que c’est généralement une mauvaise idée de faire des requêtes comme celles-ci:

 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` 

Comme votre question l’indique, la raison pour laquelle SELECT * est déconseillé est que les modifications apscopes à la table peuvent nécessiter des modifications de 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 – en général, vous voulez connaître le nombre d’enregistrements. C’est pour ça que COUNT(*) , alors utilisez-le.