Pourquoi InnoDB ne stocke-t-il pas le nombre de lignes?


19

Tout le monde sait que, dans les tables qui utilisent InnoDB comme moteur, les requêtes comme SELECT COUNT(*) FROM mytablesont très inexactes et très lentes, surtout lorsque la table s'agrandit et qu'il y a des insertions / suppressions de lignes constantes pendant l'exécution de cette requête.

Si je comprends bien, InnoDB ne stocke pas le nombre de lignes dans une variable interne, ce qui est la raison de ce problème.

Ma question est: pourquoi en est-il ainsi? Serait-il si difficile de stocker de telles informations? C'est une information importante à connaître dans tant de situations. La seule difficulté que je vois si un tel décompte interne serait implémenté est lorsque des transactions sont impliquées: si la transaction n'est pas validée, comptez-vous les lignes insérées par elle ou non?

PS: je ne suis pas un expert des bases de données, je suis juste quelqu'un qui a MySQL comme simple hobby. Donc, si je viens de demander quelque chose de stupide, ne soyez pas excessivement critique: D.


6
Lent, oui. Inexact, non. C'est lent car cela donne le résultat exact. Lorsque vous avez une table de 200 millions de lignes, et éventuellement de nombreuses autres transactions qui s'insèrent / suppriment dans la même table, éventuellement plusieurs lignes par seconde, une autre question est "avez-vous besoin du nombre exact?"
ypercubeᵀᴹ

@ypercube Je sais que j'ai vu plusieurs fois dans phpmyadmin des valeurs de comptage de lignes très différentes. De plus, il y a un commentaire disant quelque chose comme "peut-être pas exact".
Radu Murzea

1
@RaduMurzea phpMyAdmin utilise une autre méthode pour calculer le nombre de tables pour les tables InnoDB pour les raisons de vitesse que vous connaissez. C'est là que l'imprécision que vous avez mentionnée entre en jeu. Les SELECT COUNT(*) FROM ...requêtes réelles sont précises. Si vous préférez, phpMyAdmin peut être configuré pour toujours utiliser le nombre exact de lignes au détriment de la vitesse. Plus d'infos: stackoverflow.com/questions/11926259/…
DOOManiac

Réponses:


9

Je suis d'accord avec @RemusRusanu (+1 pour sa réponse)

SELECT COUNT(*) FROM mydb.mytabledans InnoDB se comporte comme un moteur de stockage transactionnel. Comparez-le à MyISAM.

MyISAM

S'il mydb.mytables'agit d'une table MyISAM, le lancement SELECT COUNT(*) FROM mydb.mytable;revient à exécuter SELECT table_rows FROM information_schema.table WHERE table_schema = 'mydb' AND table_name = 'mytable';. Cela déclenche une recherche rapide du nombre de lignes dans l'en-tête de la table MyISAM.

InnoDB

S'il mydb.mytables'agit d'une table InnoDB, vous obtenez un méli-mélo de choses qui se passent. Vous avez MVCC en cours, régissant les éléments suivants:

  • ib_logfile0 / ib_logfile1 (Rétablir les journaux)
  • ibdata1
    • Annuler les journaux
    • Rollbacks
    • Modifications du dictionnaire de données
  • Gestion du pool de tampons
  • Isolement de transaction (4 types)
    • Lectures répétables
    • Lire Engagé
    • Lire sans engagement
    • Sérialisable

Demander InnoDB pour un comptage de table nécessite une navigation à travers ces choses inquiétantes. En fait, on ne sait jamais vraiment si l'on SELECT COUNT(*) from mydb.mytablecompte uniquement les lectures répétables ou si l'on inclut les lectures qui ont été validées et celles qui ne sont pas validées.

Vous pouvez essayer de stabiliser un peu les choses en activant innodb_stats_on_metadata .

Selon la documentation MySQL sur innodb_stats_on_meta_data

Lorsque cette variable est activée (qui est la valeur par défaut, comme avant la création de la variable), InnoDB met à jour les statistiques lors des instructions de métadonnées telles que SHOW TABLE STATUS ou SHOW INDEX, ou lors de l'accès aux TABLES ou STATISTICS des tables INFORMATION_SCHEMA. (Ces mises à jour sont similaires à ce qui se passe pour ANALYZE TABLE.) Lorsqu'il est désactivé, InnoDB ne met pas à jour les statistiques pendant ces opérations. La désactivation de cette variable peut améliorer la vitesse d'accès pour les schémas qui ont un grand nombre de tables ou d'index. Il peut également améliorer la stabilité des plans d'exécution pour les requêtes impliquant des tables InnoDB.

La désactiver peut vous donner ou non un compte plus stable en termes de configuration de plans EXPLAIN. Il peut affecter les performances de SELECT COUNT(*) from mydb.mytablemanière positive, négative ou pas du tout. Essayez et voyez !!!


16

Pour le démarreur, le «nombre actuel» n'existe pas à stocker dans une variable. Une requête comme SELECT COUNT(*) FROM ...est soumise au niveau d'isolement actuel et à toutes les transactions simultanées en attente. Selon le niveau d'isolement, la requête peut voir ou non les lignes insérées ou supprimées par les transactions non validées en attente. La seule façon de répondre est de compter les lignes visibles par la transaction en cours.

Notez que je n'ai même pas abordé le sujet encore plus épineux des transactions simultanées qui commencent ou se terminent pendant le décompte. Sans parler des rollbacks ...


1
Ok, donc ça dépend du niveau d'isolement, ça a du sens. Mais il peut toujours être mis en œuvre.
Radu Murzea

@SoboLAN Il y a de nombreuses raisons pour lesquelles cela ne devrait pas & ne peut pas être, la plupart sont énumérés ci-dessus. Pourriez-vous l'implémenter en maintenant une liste de comptes par table par début de transaction (quel que soit le SCN d'Oracle dans MySQL)? La gestion de tels comptages représenterait un énorme surcoût - pensez à une base de données avec des centaines ou des milliers de sessions simultanées faisant chacune de grandes quantités d'insertions / suppressions sur la même table. Impossible à entretenir.
Philᵀᴹ

La mise en œuvre de cela est assez difficile. Pensez simplement que le nombre doit être conservé dans la base de données, cela signifie quelque part dans les métadonnées, et ce nombre doit être maintenu par chaque transaction qui insère ou supprime une ligne. Comment verrouilleriez- vous ces métadonnées? Et comment géreriez-vous les annulations? Est loin d'être anodin. Et le résultat serait utilisable pour un sous-ensemble de requêtes très très étroit.
Remus Rusanu

3
@JackDouglas Intéressant. D'après ce que j'ai vu dans le passé, les COUNT(*)requêtes sont rarement nécessaires dans la réalité et sont généralement le résultat de l'inexpérience des développeurs (comptez les lignes avant de les sélectionner!) Ou de la mauvaise conception de l'application.
Philᵀᴹ

1
@SoboLAN - non, ce ne serait pas le cas. Avoir un service qui met à jour une sorte de tableau de statistiques à des intervalles de temps prédéfinis est bien mieux. Imaginez avoir une grande base de données et plusieurs administrateurs interrogeant la plupart des tables avec SELECT COUNT(*), ajoutez un non optimisé WHEREà la table et vous aurez quelques utilisateurs mettant la base de données à genoux pour plusieurs compteurs de statistiques utiles.
NB

0

Bien qu'il soit théoriquement possible de conserver un décompte précis du nombre de lignes pour une table donnée avec InnoDB, cela se ferait au prix d'un grand nombre de verrouillages, ce qui affecterait négativement les performances. Il différerait également en fonction du niveau d'isolement.

MyISAM fait déjà le verrouillage au niveau de la table, donc pas de frais supplémentaires.

J'ai rarement besoin d'un nombre de lignes pour une table, bien que j'utilise assez COUNT (*). J'ai généralement une clause WHERE attachée. En utilisant un index efficace sur un petit ensemble de résultats, je trouve qu'ils sont assez rapides.

Je ne suis pas d'accord que les chiffres sont inexacts. Les chiffres représentent un instantané des données, et je les ai toujours trouvées exactes.

En bref, MySQL vous laisse le soin de l'implémenter pour InnoDB. Vous pouvez stocker un nombre et l'incrémenter / décrémenter après chaque requête. Cependant, la solution la plus simple est probablement de passer à MyISAM.


2
Il n'est pas possible de conserver un décompte précis des lignes dans un système transactionnel. Parce qu'il y a autant de nombres de lignes différents (et corrects) que de transactions actives.
a_horse_with_no_name

5
J'ai donné un -1 ici pour `` Bien que la solution la plus simple soit probablement de passer à MyISAM. '' Je ne recommanderais jamais de passer à MyISAM simplement pour obtenir le nombre de lignes.
Derek Downey

@a_horse_with_no_name, vous acceptez donc qu'il y ait un nombre de lignes "correct" pour chaque transaction. Cela me semble possible.
Marcus Adams

1
@DTest, je n'ai jamais dit "simplement pour obtenir le nombre de lignes".
Marcus Adams

@a_horse_with_no_name, cela ne semble pas correct. Certes, nous ne comptons le nombre de lignes que lorsque les transactions sont validées, n'est-ce pas?
Pacerier
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.