web-dev-qa-db-fra.com

Performances COUNT (*) de MySQL

J'ai une table avec plus de 15m de lignes. J'ai besoin du nombre total de lignes. Donc:

SELECT COUNT(*) FROM thetable;

Ce qui prend environ 50s pour terminer. Expliquer me donne Select tables optimized away. Je suppose que cela signifie que le résultat ne peut être trouvé qu'en utilisant un index, alors pourquoi cela prend-il encore si longtemps? Voici quelques informations sur l'index de la colonne id (il n'est pas nullable):

Type d'index: BTREE (clusterisé)

Cardinalité: 14623100

Unique: OUI

Comment puis-je améliorer les performances de cette requête? Merci.

Remarque: La base de données est MySQL 5.7.1 et utilise le moteur InnoDB.

MODIFIER:

Créer une instruction:

CREATE TABLE `properties` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `address` varchar(255) DEFAULT NULL,
  `locality` varchar(50) DEFAULT NULL,
  `latitude` decimal(13,9) DEFAULT NULL,
  `longitude` decimal(13,9) DEFAULT NULL,
  `state` varchar(10) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  .....
  PRIMARY KEY (`id`),
  KEY `index_properties_on_address` (`address`),
  KEY `index_properties_on_latitude` (`latitude`),
  KEY `index_properties_on_longitude` (`longitude`),
  KEY `index_properties_on_state` (`state`),
  KEY `index_properties_on_created_at` (`created_at`),
  .....
) ENGINE=InnoDB AUTO_INCREMENT=28267712 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED;

Remarque: j'ai omis certaines lignes, il y a 44 colonnes.

Expliquez le plan:

 + ---- + ------------- + ------- + ------------ + ---- - + --------------- + ------ + --------- + ------ + ------ + ---------- + ------------------------------ + 
 | id | select_type | table | cloisons | type | touches_ possibles | clé | key_len | ref | lignes | filtré | Extra | 
 + ---- + ------------- + ------- + ------------ + - ---- + --------------- + ------ + --------- + ------ + ----- - + ---------- + ------------------------------ + 
 | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Sélectionnez les tables optimisées loin | 
 + ---- + ------------- + ------- + ------------ + ------ + --------------- + ------ + --------- + ------ + - ---- + ---------- + ------------------------------ + 
7
lunr

Retour lorsque mysql n'était pas transactionnellement sain par défaut (lorsque les gens utilisaient régulièrement des tables myISAM au lieu d'InnoDB parce que c'était la valeur par défaut ou, remontant dans le temps, car il n'existait pas encore) "SELECT * FROM some_table" sans aucune clause de filtrage était l'un des types de requêtes sur lesquels Peopel se penchait sur le fait que mySQL était beaucoup plus rapide que les autres moteurs de base de données.

Dans un environnement transactionnellement sûr, le moteur de base de données devra vérifier chaque ligne et s'assurer qu'elle doit être visible pour la session en cours (c'est-à-dire qu'elle ne fait pas partie d'une transaction qui n'est pas encore validée (ou qui n'a pas été validée à au début de cette session, transaction active) ou est en cours de restauration) - la vérification de chaque ligne implique la nécessité d'effectuer une analyse de table ou (le cas échéant) une analyse d'index en cluster.

Il serait possible pour le moteur de garder une trace du nombre de lignes visibles dans chaque objet pour chaque session/transaction active, mais sans doute les concepteurs n'ont pas a jugé que cela valait le traitement supplémentaire impliqué, donc je suppose que cela n'est généralement pas considéré pratique - je peux imaginer qu'il y aurait des exigences de verrouillage assez complexes à gérer avec une concurrence qui nuirait trop aux performances d'autres opérations. Vous pouvez l'implémenter vous-même en conservant une table dans laquelle est enregistré le nombre de lignes dans la table d'intérêt, et que tout votre code maintienne méticuleusement cette valeur, mais ce serait assez compliqué et pourrait être excessivement sujet aux erreurs dues à bogues signifiant que le nombre dériverait de vrai au fil du temps (et vous ajoutez probablement une source de blocage potentiel et/ou un goulot d'étranglement de verrouillage au niveau de la couche d'application).

Les situations dans lesquelles la sécurité au niveau de la ligne est utilisée compliquent encore plus cela - ainsi que la nécessité de vérifier l'état d'une ligne/page par rapport à la transaction en cours, le moteur doit également vérifier à nouveau l'utilisateur actuel et les règles de sécurité sont dynamique, il ne serait pas pratique de mettre ces informations en cache, ce qui nécessiterait en outre une analyse à chaque fois juste au cas où. La sécurité au niveau des lignes est ajoutée à MS SQL Server dans la prochaine version ( https://msdn.Microsoft.com/en-us/library/dn765131.aspx ) et est déjà présente dans postgres ( http://www.postgresql.org/docs/9.5/static/ddl-rowsecurity.html ), je ne connais pas son statut dans les autres SGBDR.

6
David Spillett

En complément de la réponse de @ david-spillett, vous pouvez modifier votre requête en remplaçant simplement le count(*) par un count(id) sur votre requête, devenant ainsi:

SELECT COUNT(id) FROM thetable;

Étant donné que la colonne id n'est pas nulle, indexée (en fait, c'est la clé primaire), ce qui signifie qu'elle n'est pas nulle pour toutes les lignes et, comme tel, il y a autant de ids qu'il y en a Lignes.

Mais, même si vous remplacez count(*) par count(0) ou count("Hi, I'm a row"), vous obtiendrez les mêmes performances, car en interne, elles entraînent la même opération. Vous pouvez le vérifier en comparant le résultat d'un EXPLAIN EXTENDED ... Sur toutes les requêtes:

EXPLAIN EXTENDED SELECT COUNT(*) FROM thetable;
EXPLAIN EXTENDED SELECT COUNT(id) FROM thetable;
EXPLAIN EXTENDED SELECT COUNT(0) FROM thetable;
EXPLAIN EXTENDED SELECT COUNT("Hi, I'm a row") FROM thetable;

Actuellement pour InnoDB, select count(<whatever>) from table_name ;, sans aucune condition, n'est pas la meilleure pratique.

Ce type de requête fonctionne mieux lorsque:

  1. Votre plus petit index sur la table se trouve sur une très petite colonne (un tinyint, par exemple) au lieu d'un index composé ou sur une grande colonne (comme un varchar(200)), mais ne l'ajoutez pas juste pour améliorer cela type de sélections. En effet, avec un index plus petit, InnoDB a moins de données à analyser;
  2. Vous ajoutez un critère WHERE, en réduisant les lignes à compter. C'est votre meilleure option.
3
Nuno Pereira

Créez une nouvelle table (properties_count (id, count)) et utilisez le déclencheur pour l'insertion (incrémentation) et pour la suppression (décrémentation).

Après, vous pouvez utiliser: sélectionnez count dans properties_count.

1

si vous pouviez profiler cette requête, nous pourrions avoir plus d'informations sur ce problème. Une chose est sûre, puisque le moteur de stockage est InnoDB, les tampons innodb ont un impact.

0
Adarsh Gangadharan