web-dev-qa-db-fra.com

Performances MySQL: plusieurs tables vs index sur une seule table et partitions

Je me demande ce qui est plus efficace et plus rapide en termes de performances:
Avoir un index sur une grande table ou plusieurs tables plus petites sans index?

Comme il s'agit d'un problème assez abstrait, permettez-moi de le rendre plus pratique:
J'ai un tableau avec des statistiques sur les utilisateurs (20 000 utilisateurs et environ 30 millions de lignes au total). Le tableau comprend environ 10 colonnes, y compris le user_id, actions, timestamps, etc.
Les applications les plus courantes sont: l'insertion de données par user_id et la récupération des données par user_id (SELECT les instructions n'incluent jamais plusieurs user_id's).

Jusqu'à présent, j'ai un INDEX sur le user_id et la requête ressemble à ceci

SELECT * FROM statistics WHERE user_id = 1

Maintenant, avec de plus en plus de lignes, le tableau devient de plus en plus lent. Les instructions INSERT ralentissent car les INDEX grossissent de plus en plus; Les instructions SELECT ralentissent, car il y a plus de lignes à parcourir.

Maintenant, je me demandais pourquoi ne pas avoir une table de statistiques pour chaque utilisateur et remplacer la syntaxe de requête par quelque chose comme ceci à la place:

SELECT * FROM statistics_1

1 représente le user_id évidemment.
De cette façon, aucune INDEX n'est nécessaire et il y a beaucoup moins de données dans chaque table, donc les instructions INSERT et SELECT devraient être beaucoup plus rapides.

Maintenant mes questions encore:
Y a-t-il des inconvénients réels à gérer autant de tables (dans mon cas 20 000) au lieu d'utiliser une table avec un INDEX?
Mon approche accélérerait-elle réellement les choses ou la recherche de la table pourrait-elle éventuellement ralentir les choses plus que tout?

30
Horen

Créer 20 000 tables est une mauvaise idée. Vous aurez besoin de 40000 tables avant longtemps, et plus encore.

J'ai appelé ce syndrome Metadata Tribbles dans mon livre SQL Antipatterns . Vous voyez cela se produire chaque fois que vous prévoyez de créer une "table par X" ou une "colonne par X".

Cela pose de réels problèmes de performances lorsque vous avez des dizaines de milliers de tables. Chaque table nécessite MySQL pour maintenir des structures de données internes, des descripteurs de fichiers, un dictionnaire de données, etc.

Il y a également des conséquences opérationnelles pratiques. Voulez-vous vraiment créer un système qui vous oblige à créer une nouvelle table chaque fois qu'un nouvel utilisateur s'inscrit?

Au lieu de cela, je vous recommande d'utiliser Partitionnement MySQL .

Voici un exemple de partitionnement de la table:

CREATE TABLE statistics (
  id INT AUTO_INCREMENT NOT NULL,
  user_id INT NOT NULL,
  PRIMARY KEY (id, user_id)
) PARTITION BY HASH(user_id) PARTITIONS 101;

Cela vous donne l'avantage de définir une table logique, tout en divisant la table en plusieurs tables physiques pour un accès plus rapide lorsque vous recherchez une valeur spécifique de la clé de partition.

Par exemple, lorsque vous exécutez une requête comme votre exemple, MySQL accède uniquement à la partition correcte contenant l'ID utilisateur spécifique:

mysql> EXPLAIN PARTITIONS SELECT * FROM statistics WHERE user_id = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: statistics
   partitions: p1    <--- this shows it touches only one partition 
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 8
          ref: NULL
         rows: 2
        Extra: Using where; Using index

La méthode de partitionnement HASH signifie que les lignes sont placées dans une partition par un module de la clé de partition entière. Cela signifie que de nombreux utilisateurs_id sont mappés sur la même partition, mais chaque partition n'aurait en moyenne que 1/Nème de lignes (où N est le nombre de partitions). Et vous définissez la table avec un nombre constant de partitions, vous n'avez donc pas à l'étendre à chaque fois que vous obtenez un nouvel utilisateur.

Vous pouvez choisir n'importe quel nombre de partitions jusqu'à 1024 (ou 8192 dans MySQL 5.6), mais certaines personnes ont signalé des problèmes de performances lorsqu'elles montent aussi haut.

Il est recommandé d'utiliser un nombre premier de partitions. Dans le cas où vos valeurs user_id suivent un modèle (comme utiliser uniquement des nombres pairs), l'utilisation d'un nombre premier de partitions permet de répartir les données plus uniformément.


Re vos questions en commentaire:

Comment pourrais-je déterminer un nombre raisonnable de partitions?

Pour le partitionnement HASH, si vous utilisez 101 partitions comme je le montre dans l'exemple ci-dessus, alors une partition donnée a en moyenne environ 1% de vos lignes. Vous avez dit que votre tableau de statistiques comptait 30 millions de lignes, donc si vous utilisez ce partitionnement, vous n'auriez que 300 000 lignes par partition. C'est beaucoup plus facile à lire pour MySQL. Vous pouvez (et devez) également utiliser des index - chaque partition aura son propre index, et il ne sera que de 1% aussi grand que l'index sur l'ensemble de la table non partitionnée.

La réponse à la question de savoir comment déterminer un nombre raisonnable de partitions est donc la suivante: quelle est la taille de votre table et quelle taille voulez-vous que les partitions soient en moyenne?

La quantité de partitions ne devrait-elle pas augmenter avec le temps? Si oui: comment automatiser cela?

Le nombre de partitions n'a pas nécessairement besoin d'augmenter si vous utilisez le partitionnement HASH. Finalement, vous pouvez avoir un total de 30 milliards de lignes, mais j'ai constaté que lorsque votre volume de données augmente par ordre de grandeur, cela exige de toute façon une nouvelle architecture. Si vos données deviennent aussi volumineuses, vous avez probablement besoin de partitionnement sur plusieurs serveurs ainsi que de partitionnement en plusieurs tables.

Cela dit, vous pouvez re-partitionner une table avec ALTER TABLE:

ALTER TABLE statistics PARTITION BY HASH(user_id) PARTITIONS 401;

Cela doit restructurer la table (comme la plupart des modifications ALTER TABLE), alors attendez-vous à ce que cela prenne un certain temps.

Vous souhaiterez peut-être surveiller la taille des données et des index dans les partitions:

SELECT table_schema, table_name, table_rows, data_length, index_length
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE partition_method IS NOT NULL;

Comme avec n'importe quelle table, vous voulez que la taille totale des index actifs tienne dans votre pool de tampons, car si MySQL doit permuter des parties d'index dans et hors du pool de tampons pendant les requêtes SELECT, les performances en souffrent.

Si vous utilisez le partitionnement RANGE ou LIST, ajouter, supprimer, fusionner et fractionner des partitions est beaucoup plus courant. Voir http://dev.mysql.com/doc/refman/5.6/en/partitioning-management-range-list.html

Je vous encourage à lire la section manuelle sur le partitionnement , et à consulter également cette belle présentation: Boost Performance With MySQL 5.1 Partitions .

82
Bill Karwin

Cela dépend probablement du type de requêtes que vous prévoyez de faire souvent, et la meilleure façon de savoir avec certitude est de simplement implémenter un prototype des deux et de faire des tests de performances.

Cela dit, je m'attendrais à ce qu'une seule (grande) table avec un index fasse mieux dans l'ensemble parce que la plupart des systèmes SGBD sont fortement optimisés pour faire face à la situation exacte de trouver et d'insérer des données dans de grandes tables. Si vous essayez de créer de nombreuses petites tables dans l'espoir d'améliorer les performances, vous combattez l'optimiseur (ce qui est généralement mieux).

Gardez également à l'esprit qu'une table est probablement plus pratique pour l'avenir. Et si vous souhaitez obtenir des statistiques agrégées sur tous les utilisateurs? Avoir 20 000 tables rendrait cela très difficile et inefficace à exécuter. Il convient également de considérer la flexibilité de ces schémas. Si vous partitionnez vos tables comme ça, vous pourriez vous concevoir dans un coin pour l'avenir.

4
Oleksi

Il y a peu à ajouter à la réponse de Bill Karwins. Mais une astuce est la suivante: vérifiez si toutes les données de l'utilisateur sont nécessaires en détail et tout le temps.

Si vous voulez donner des statistiques d'utilisation ou le nombre de visites ou ces choses, vous n'obtiendrez généralement pas une granularité d'actions uniques et de secondes pour, disons, l'année 2009 de la vue d'aujourd'hui. Vous pouvez donc créer des tables d'agrégation et une table d'archives (pas d'archive de moteur, bien sûr) pour avoir les données récentes sur la base d'actions et un aperçu des actions plus anciennes.

Les vieilles actions ne changent pas, je pense.

Et vous pouvez toujours entrer dans les détails de l'agrégation avec un week_id dans la table d'archives par exemple.

1
flaschenpost

Au lieu de passer d'une table à une table par utilisateur, vous pouvez utiliser le partitionnement pour atteindre un certain nombre de tables/rapport de taille de table quelque part au milieu.

Vous pouvez également conserver des statistiques sur les utilisateurs pour essayer de déplacer les utilisateurs "actifs" dans 1 table afin de réduire le nombre de tables auxquelles vous devez accéder au fil du temps.

L'essentiel est qu'il y a beaucoup de choses que vous pouvez faire, mais en grande partie vous devez construire des prototypes et des tests et simplement évaluer les impacts sur les performances des différents changements que vous apportez.

0
E Smith