Comment accélérer les requêtes sur une grande table de 220 millions de lignes (9 gig data)?

31

Le problème:

Nous avons un site social où les membres peuvent s'évaluer pour la compatibilité ou la correspondance. Ce user_match_ratingstableau contient plus de 220 millions de lignes (9 gig data ou presque 20 gig dans les index). Les requêtes sur cette table s'affichent régulièrement dans slow.log (seuil> 2 secondes) et constituent la requête lente la plus fréquemment enregistrée dans le système:

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 1051
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 395357 group by rating;"

Query_time: 4  Lock_time: 0  Rows_sent: 3  Rows_examined: 1294
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 4182969 group by rating;"

Query_time: 3  Lock_time: 0  Rows_sent: 3  Rows_examined: 446
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 630148 group by rating;"

Query_time: 5  Lock_time: 0  Rows_sent: 3  Rows_examined: 3788
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1835698 group by rating;"

Query_time: 17  Lock_time: 0  Rows_sent: 3  Rows_examined: 4311
"select rating, count(*) as tally from user_match_ratings where rated_user_id = 1269322 group by rating;"

Version de MySQL:

  • version du protocole: 10
  • version: 5.0.77-log
  • version bdb: Sleepycat Software: Berkeley DB 4.1.24: (29 janvier 2009)
  • version compiler la machine: x86_64 version_compile_os: redhat-linux-gnu

Informations sur la table:

SHOW COLUMNS FROM user_match_ratings;

Donne:

╔═══════════════╦════════════╦════╦═════╦════════╦════════════════╗
 id             int(11)     NO  PRI  NULL    auto_increment 
 rater_user_id  int(11)     NO  MUL  NULL                   
 rated_user_id  int(11)     NO  MUL  NULL                   
 rating         varchar(1)  NO       NULL                   
 created_at     datetime    NO       NULL                   
╚═══════════════╩════════════╩════╩═════╩════════╩════════════════╝

Exemple de requête:

select * from mutual_match_ratings where id=221673540;

donne:

╔═══════════╦═══════════════╦═══════════════╦════════╦══════════════════════╗
 id         rater_user_id  rated_user_id  rating  created_at           
╠═══════════╬═══════════════╬═══════════════╬════════╬══════════════════════╣
 221673540  5699713        3890950        N       2013-04-09 13:00:38  
╚═══════════╩═══════════════╩═══════════════╩════════╩══════════════════════╝

Les index

La table comporte 3 index:

  1. index unique sur rated_user_id
  2. indice composite sur rater_user_idetcreated_at
  3. indice composite sur rated_user_idetrater_user_id
affichez l'index de user_match_ratings;

donne:

╔════════════════════╦════════════╦═══════════════════════════╦══════════════╦═══════════════╦═══════════╦═════════════╦══════════╦════════╦═════════════════════════╦════════════╦══════════════════╗
 Table               Non_unique  Key_name                   Seq_in_index  Column_name    Collation  Cardinality  Sub_part  Packed  Null                     Index_type  Comment          
╠════════════════════╬════════════╬═══════════════════════════╬══════════════╬═══════════════╬═══════════╬═════════════╬══════════╬════════╬═════════════════════════╬════════════╬══════════════════╣
 user_match_ratings  0           PRIMARY                    1             id             A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  1             rater_user_id  A          11039059     NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index1  2             created_at     A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  1             rated_user_id  A          4014203      NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index2  2             rater_user_id  A          220781193    NULL      NULL    BTREE                                                 
 user_match_ratings  1           user_match_ratings_index3  1             rated_user_id  A          2480687      NULL      NULL    BTREE                                                 
╚════════════════════╩════════════╩═══════════════════════════╩══════════════╩═══════════════╩═══════════╩═════════════╩══════════╩════════╩═════════════════════════╩════════════╩══════════════════╝

Même avec les index, ces requêtes sont lentes.

Ma question:

Est-ce que la séparation de cette table / données vers une autre base de données sur un serveur qui a suffisamment de RAM pour stocker ces données en mémoire accélérerait-elle ces requêtes? Y a-t-il quoi que ce soit dans la façon dont les tables / index sont configurés que nous pouvons améliorer pour accélérer ces requêtes?

Actuellement, nous avons 16 Go de mémoire; Cependant, nous envisageons de mettre à niveau la machine existante à 32 Go ou d'ajouter une nouvelle machine avec au moins autant, peut-être des disques SSD.

Ranknoodle
la source
1
Votre question est incroyable. Je suis très intéressé par votre solution actuelle, comment avez-vous réussi à obtenir un résultat en <= 2 secondes? Parce que j'ai une table qui n'a que 20 millions d'enregistrements et cela prend encore 30 secondes SELECT QUERY. Souhaitez-vous s'il vous plaît suggérer? PS Votre question m'a forcé à rejoindre cette communauté (y);)
NullPointer
2
Regardez les index sur la table que vous interrogez. Souvent, de nombreuses améliorations peuvent être apportées aux requêtes en créant l'index approprié. Pas toujours, mais vu de nombreuses instances où les requêtes sont rendues rapides en fournissant un index par rapport aux colonnes de la clause where d'une requête. Surtout si une table grandit de plus en plus.
Ranknoodle
Bien sûr, @Ranknoodle. Merci. Je vérifierai respectivement.
NullPointer

Réponses:

28

Réflexions sur la question, jetées dans un ordre aléatoire:

  • L'indice évident pour cette requête est: (rated_user_id, rating). Une requête qui obtient des données pour un seul million d'utilisateurs et nécessite 17 secondes fait quelque chose de mal: lire dans l' (rated_user_id, rater_user_id)index puis lire dans le tableau les valeurs (des centaines à des milliers) pour la ratingcolonne, comme ratingdans aucun index. Ainsi, la requête doit lire de nombreuses lignes de la table qui se trouvent dans de nombreux emplacements de disque différents.

  • Avant de commencer à ajouter de nombreux index dans les tableaux, essayez d'analyser les performances de l'ensemble de la base de données, de l'ensemble des requêtes lentes, réexaminez les choix des types de données, le moteur que vous utilisez et les paramètres de configuration.

  • Pensez à passer à une version plus récente de MySQL, 5.1, 5.5 ou même 5.6 (également: versions Percona et MariaDB.) (comme 10 millisecondes). Cela vous donnera de bien meilleures informations sur les requêtes lentes.

  • Le choix du type de données de ratingest bizarre. VARCHAR(1)? Pourquoi ne pas CHAR(1)? Pourquoi ne pas TINYINT? Cela vous fera économiser de l'espace, à la fois dans la table et dans les index qui incluront (incluront) cette colonne. Une colonne varchar (1) a besoin d'un octet de plus sur char (1) et si elles sont utf8, les colonnes (var) char auront besoin de 3 (ou 4) octets, au lieu de 1 (tinyint).

ypercubeᵀᴹ
la source
2
Quel est l'impact sur les performances ou le gaspillage de stockage en termes de% si vous utilisez le mauvais type de données?
FlyingAtom
1
@FlyingAtom Cela dépend du cas, mais pour certaines colonnes indexées qui doivent encore être analysées (par exemple lorsque vous n'avez pas de clause where mais que vous récupérez uniquement cette colonne), le moteur peut décider d'analyser l'index au lieu de la table, et si vous optimisez votre type de données dans une moitié de la taille, alors l'analyse serait deux fois plus rapide et la réponse serait la moitié de la taille. Si vous analysez toujours la table au lieu d'un index (par exemple, lorsque vous récupérez plus de colonnes, pas seulement celles de l'index), les avantages seraient moins importants.
Sebastián Grignoli
-1

J'ai géré des tables pour le gouvernement allemand avec parfois 60 millions d'enregistrements.

Nous avions beaucoup de ces tables.

Et nous avions besoin de connaître plusieurs fois le nombre total de lignes d'une table.

Après avoir parlé avec les programmeurs Oracle et Microsoft, nous n'étions pas si heureux ...

Ainsi, nous, le groupe des programmeurs de bases de données, avons décidé que dans chaque table est un enregistrement toujours l'enregistrement dans lequel le nombre total d'enregistrements est stocké. Nous avons mis à jour ce nombre, en fonction des lignes INSERT ou DELETE.

Nous avons essayé toutes les autres façons. C'est de loin le moyen le plus rapide.

Nous utilisons cette méthode depuis 1998 et n'avons jamais eu de mauvais nombre de lignes, dans toutes nos millions de tables d'enregistrement.

FrankyBkk
la source
7
Je suggérerais d'examiner certaines des fonctionnalités introduites au cours des 18 dernières années. Entre autres, count(*)a quelques améliorations.
dezso
Comment savez-vous que vous n'avez jamais eu de mauvais numéro si vous ne pouviez pas les compter? euhmmmm ...
Tonca
-3

Je vais essayer de partitionner les types de notation, comme:

mutual_match_ratings_N, mutual_match_ratings_S, etc.

Vous devez effectuer une requête pour chaque type, mais c'est peut-être plus rapide que dans l'autre sens. Essaie.

Cela suppose que vous avez un nombre fixe de types de notation et que vous n'avez pas besoin de ce tableau pour d'autres requêtes qui seraient pires avec cette nouvelle structure.

Si tel est le cas, vous devez rechercher une autre approche ou conserver deux copies de la table (votre table initiale et celles partitionnées) si cela est abordable en termes d'espace et de maintenabilité (ou logique d'application).

appartisan
la source