Comment accélérer & # 8220; sélectionner le compte (*) & # 8221; avec le groupe & # 8220; groupé par & # 8221; et & # 8220; où & # 8221 ;?

StackOverflow https://stackoverflow.com/questions/1031312

Question

Comment accélérer sélectionner le nombre (*) avec groupe par ?
C'est trop lent et est utilisé très fréquemment.
J'ai un gros problème avec select count (*) et groupe par avec une table ayant plus de 3 000 000 de lignes.

select object_title,count(*) as hot_num   
from  relations 
where relation_title='XXXX'   
group by object_title  

titre_relation , titre_objet est varchar. where relation_title = 'XXXX' , qui renvoie plus de 1 000 000 lignes, les index sur object_title pourraient ne pas fonctionner correctement.

Était-ce utile?

La solution

Voici plusieurs choses que j'essaierais, par ordre de difficulté croissante:

(facile) - Assurez-vous de disposer du bon indice de couverture

CREATE INDEX ix_temp ON relations (relation_title, object_title);

Ceci devrait maximiser perf compte tenu de votre schéma existant, car (sauf si votre version de l'optimiseur de MySQL est vraiment stupide!), elle minimisera le nombre d'E / S nécessaires pour satisfaire votre requête (contrairement à l'index dans l'ordre inverse où l’intégralité de l’index doit être analysée) et elle couvrira la requête afin que vous n’ayez pas à toucher l’index clusterisé.

(un peu plus difficile) - assurez-vous que vos champs varchar sont aussi petits que possible

L’un des problèmes rencontrés avec les index varchar sur MySQL est que, lors du traitement d’une requête, la taille totale déclarée du champ est extraite dans la RAM. Donc, si vous avez un varchar (256) mais utilisez seulement 4 caractères, vous payez toujours l'utilisation de la RAM de 256 octets pendant le traitement de la requête. Aie! Donc, si vous pouvez facilement réduire vos limites varchar, cela devrait accélérer vos requêtes.

(plus difficile) - Normaliser

30% de vos lignes ayant une seule valeur de chaîne est un cri clair pour la normalisation dans une autre table afin que vous ne dupliquiez pas les chaînes des millions de fois. Envisagez de normaliser trois tables et d’utiliser des ID entiers pour les joindre.

Dans certains cas, vous pouvez normaliser sous les couvertures et masquer la normalisation avec des vues correspondant au nom de la table en cours ... il vous suffit alors de rendre vos requêtes INSERT / UPDATE / DELETE conscientes de la normalisation, mais elles peuvent quitter. vos SELECTs seuls.

(hardest) - Hachez les colonnes de votre chaîne et indexez les hachages

Si normaliser signifie modifier trop de code, mais que vous pouvez modifier légèrement votre schéma, vous pouvez envisager de créer des hachages 128 bits pour vos colonnes de chaîne (à l'aide de fonction MD5 ). Dans ce cas (contrairement à la normalisation), vous n'avez pas à changer toutes vos requêtes, uniquement les INSERT et certains des SELECT. Quoi qu’il en soit, vous voudrez hacher vos champs de chaîne, puis créer un index sur les hachages, par exemple.

CREATE INDEX ix_temp ON relations (relation_title_hash, object_title_hash);

Notez que vous devez vous amuser avec le SELECT pour vous assurer que vous effectuez le calcul via l'index de hachage et que vous n'extrayez pas l'index en cluster (nécessaire pour résoudre la valeur textuelle de object_title afin de satisfaire la requête). ).

De plus, si relation_title a une petite taille varchar mais que le titre de l'objet a une taille longue, vous pouvez potentiellement hacher uniquement object_title et créer l'index sur (relation_title, object_title_hash) . .

Notez que cette solution n’est utile que si l’un ou les deux champs sont très longs par rapport à la taille des hachages.

Notez également que le hachage a des effets intéressants sur la sensibilité à la casse / le classement, car le hachage d’une chaîne minuscule n’est pas identique à celui d’une chaîne de caractères majuscule. Vous devez donc vous assurer que vous appliquez la canonisation aux chaînes avant de les hacher - en d'autres termes, n'alignez que des minuscules si vous vous trouvez dans une base de données insensible à la casse. Vous voudrez peut-être aussi couper les espaces au début ou à la fin, en fonction de la manière dont votre base de données traite les espaces de début / fin.

Autres conseils

Indexer les colonnes de la clause GROUP BY serait la première chose à essayer, en utilisant un index composite. Une telle requête peut potentiellement être traitée en utilisant uniquement les données d'index, évitant ainsi le besoin d'analyser la table. Comme les enregistrements de l'index sont triés, le SGBD ne devrait pas avoir besoin d'effectuer un tri séparé dans le cadre du traitement du groupe. Cependant, l'index ralentira les mises à jour de la table. Soyez donc prudent si votre table subit de nombreuses mises à jour.

Si vous utilisez InnoDB pour le stockage de la table, les lignes de la table seront mises en cluster physiquement par l'index de clé primaire. Si cela (ou une partie de celui-ci) correspond à votre clé GROUP BY, cela devrait accélérer une requête telle que celle-ci, car les enregistrements associés seront extraits ensemble. Là encore, cela évite d’avoir à effectuer un tri séparé.

En général, les index bitmap seraient une autre solution efficace, mais à ce que je sache, MySQL ne les prend pas en charge.

Une vue matérialisée serait une autre approche possible, mais là encore, cela n’est pas supporté directement dans MySQL. Toutefois, si vous n'exigiez pas que les statistiques COUNT soient complètement à jour, vous pouvez exécuter périodiquement une instruction CREATE TABLE ... AS SELECT ... pour mettre en cache manuellement les résultats. C’est un peu moche car ce n’est pas transparent, mais peut être acceptable dans votre cas.

Vous pouvez également gérer une table de cache de niveau logique à l'aide de déclencheurs. Cette table aurait une colonne pour chaque colonne de votre clause GROUP BY, avec une colonne Count pour stocker le nombre de lignes pour cette valeur de clé de regroupement particulière. Chaque fois qu'une ligne est ajoutée ou mise à jour dans la table de base, insérez ou incrémentez / décrémentez la ligne de compteur dans la table récapitulative pour cette clé de regroupement particulière. Cela peut être meilleur que la fausse approche matérialisée, car le résumé mis en cache sera toujours à jour, et chaque mise à jour est effectuée de manière incrémentielle et devrait avoir un impact moindre sur les ressources. Cependant, je pense que vous devrez faire attention aux conflits de verrous sur la table de cache.

Si vous avez InnoDB, count (*) et toute autre fonction d'agrégation effectueront un balayage de table. Je vois quelques solutions ici:

  1. Utilisez les déclencheurs et stockez les agrégats dans une table séparée. Avantages: intégrité. Inconvénients: mises à jour lentes
  2. Utilisez les files d'attente de traitement. Avantages: mises à jour rapides. Inconvénients: l’ancien état peut persister jusqu’à ce que la file d’attente soit traitée afin que l’utilisateur puisse ressentir un manque d’intégrité.
  3. Séparez complètement la couche d'accès au stockage et stockez les agrégats dans un tableau séparé. La couche de stockage sera au courant de la structure des données et pourra appliquer des deltas au lieu d'effectuer des comptages complets. Par exemple, si vous fournissez un " addObject " vous saurez quand un objet a été ajouté et l’agrégat serait affecté. Ensuite, vous ne faites qu'un ensemble de tables de mise à jour count = count + 1 . Avantages: mises à jour rapides, intégrité (vous pouvez utiliser un verrou si plusieurs clients peuvent modifier le même enregistrement). Inconvénients: vous combinez un peu de logique métier et de stockage.

Je vois que quelques personnes ont demandé quel moteur vous utilisiez pour la requête. Je vous recommande vivement d’utiliser MyISAM pour les raisons suivantes:

InnoDB - @Sorin Mocanu a correctement identifié le fait que vous effectuerez une analyse complète de la table, quels que soient les index.

MyISAM : conserve toujours le nombre de lignes actuel à portée de main.

Enfin, comme @justin l'a indiqué, assurez-vous de disposer du bon indice de couverture:

CREATE INDEX ix_temp ON relations (relation_title, object_title);

test  count (myprimaryindexcolumn) et comparez les performances à votre nombre (*)

il y a un moment où vous avez vraiment besoin plus de RAM / CPU / IO. Vous avez peut-être touché cela pour votre matériel.

Je noterai qu’il n’est généralement pas efficace d’utiliser des index (sauf s’ils sont couvrant) pour les requêtes qui touchent plus de 1 à 2% du nombre total de lignes d’un tableau. Si votre requête volumineuse effectue des recherches dans l'index et dans le signet, il se peut qu'elle en raison d'un plan mis en cache qui était juste à partir d'une requête de jour total. Essayez d'ajouter dans WITH (INDEX = 0) pour forcer un balayage de table et voir s’il est plus rapide.

prenez ceci à partir de: http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sqlserver.programming&tid=4631bab4- 0104-47aa-b548-e8428073b6e6 & amp = cat = & amp; lang = & amp; cr = & amp; sloc = & amp; p = 1

Si vous voulez connaître la taille de la table entière, vous devriez interroger les méta-tables ou le schéma d’informations (qui existent dans tous les SGBD que je connais, mais je ne suis pas sûr de MySQL). Si votre requête est sélective, vous devez vous assurer qu’il existe un index.

Autant que je sache, vous ne pouvez rien faire de plus.

Je suggérerais d'archiver les données sauf s'il existe une raison spécifique de les conserver dans la base de données ou de partitionner les données et d'exécuter les requêtes séparément.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top