Question

Existe-t-il une différence de performance mesurable entre l'utilisation de INT et de VARCHAR comme clé primaire dans MySQL? J'aimerais utiliser VARCHAR comme clé primaire pour les listes de références (pensez aux États-Unis, codes de pays) et un collègue ne bougera pas sur INT AUTO_INCREMENT en tant que clé primaire pour toutes les tables.

Mon argument, tel que détaillé ici , la différence de performances entre INT et VARCHAR est négligeable, car chaque référence de clé étrangère INT nécessite un JOIN pour donner un sens à la référence, une clé VARCHAR présentera directement les informations.

Alors, est-ce que quelqu'un a une expérience de ce cas d'utilisation particulier et des problèmes de performances associés?

Était-ce utile?

La solution

Vous indiquez que vous pouvez éviter un certain nombre de requêtes jointes en utilisant ce que l'on appelle un clé naturelle au lieu d'une clé de substitution . Vous êtes le seul à pouvoir évaluer si l’avantage que vous en retirez est important pour votre application.

En d’autres termes, vous pouvez mesurer dans votre application les requêtes les plus importantes pour la rapidité, car elles fonctionnent avec de gros volumes de données ou sont exécutées très fréquemment. Si ces requêtes ont l'avantage d'éliminer une jointure et qu'elles ne souffrent pas de l'utilisation d'une clé primaire varchar, faites-le.

N'utilisez aucune de ces stratégies pour toutes les tables de votre base de données. Il est probable que dans certains cas, une clé naturelle est préférable, mais dans d'autres cas, une clé de substitution est préférable.

D'autres personnes soulignent qu'il est rare en pratique qu'une clé naturelle ne change jamais ou ait des doublons, donc les clés de substitution en valent généralement la peine.

Autres conseils

Ce n’est pas une question de performance. Il s'agit de ce qui fait une bonne clé primaire. Unique et immuable dans le temps. Vous pensez peut-être qu'une entité telle qu'un code de pays ne change jamais au fil du temps et serait un bon candidat pour une clé primaire. Mais l'expérience amère est qu'il en est rarement ainsi.

INT AUTO_INCREMENT répond aux "unique et invariable dans le temps" état. D'où la préférence.

Dépend de la longueur. Si varchar contient 20 caractères et que int correspond à 4, si vous utilisez un int, votre index aura CINQ fois plus de nœuds par page d'espace d'index sur le disque ... signifie que parcourir l'index nécessitera un cinquième du nombre de lectures physiques et / ou logiques.

Ainsi, si les performances posent un problème, utilisez toujours une clé intégrale non significative (appelée un substitut) pour vos tables et pour les clés étrangères qui référencent les lignes de ces tables ...

Parallèlement, , pour garantir la cohérence des données, chaque table, dans la mesure du possible, doit également comporter une clé alternative non-significative, (ou un index unique) pour s’assurer que les lignes en double ne peuvent pas être insérées (dupliquer en fonction d’attributs de table significatifs).

Pour l’utilisation spécifique dont vous parlez (comme les recherches d’état), cela n’a vraiment aucune importance, car la taille de la table est si petite. En général, il n’ya aucune incidence sur les performances des indices mille rangs ...

Absolument pas.

J'ai effectué plusieurs ... plusieurs ... vérifications de performances entre INT, VARCHAR et CHAR.

10 millions de tables d’enregistrement avec une clé PRIMARY KEY (unique et en cluster) ont exactement la même vitesse et les mêmes performances (et le même coût pour les sous-arbres), quel que soit le modèle que j’ai utilisé.

Cela étant dit ... utilisez ce qui convient le mieux à votre application. Ne vous inquiétez pas pour la performance.

Le manque de repères pour cette mise en ligne me contrariait un peu, alors j’ai fait un test moi-même.

Notez cependant que je ne le fais pas de manière régulière. Veuillez donc vérifier dans ma configuration et mes étapes tous les facteurs qui auraient pu influencer les résultats de manière non intentionnelle, puis publiez vos préoccupations dans des commentaires.

La configuration était la suivante:

  • CPU Intel® Core ™ i7-7500U à 2,70 GHz × 4
  • 15,6 Go de mémoire vive, dont je me suis assuré que 8 Go environ étaient libres pendant le test.
  • Lecteur SSD de 148,6 Go, avec beaucoup d’espace libre.
  • Ubuntu 16.04 64 bits
  • MySQL Ver 14.14 Distrib 5.7.20, pour Linux (x86_64)

Les tableaux:

create table jan_int (data1 varchar(255), data2 int(10), myindex tinyint(4)) ENGINE=InnoDB;
create table jan_int_index (data1 varchar(255), data2 int(10), myindex tinyint(4), INDEX (myindex)) ENGINE=InnoDB;
create table jan_char (data1 varchar(255), data2 int(10), myindex char(6)) ENGINE=InnoDB;
create table jan_char_index (data1 varchar(255), data2 int(10), myindex char(6), INDEX (myindex)) ENGINE=InnoDB;
create table jan_varchar (data1 varchar(255), data2 int(10), myindex varchar(63)) ENGINE=InnoDB;
create table jan_varchar_index (data1 varchar(255), data2 int(10), myindex varchar(63), INDEX (myindex)) ENGINE=InnoDB;

Ensuite, j'ai rempli 10 millions de lignes dans chaque table avec un script PHP dont l'essence est la suivante:

$pdo = get_pdo();

$keys = [ 'alabam', 'massac', 'newyor', 'newham', 'delawa', 'califo', 'nevada', 'texas_', 'florid', 'ohio__' ];

for ($k = 0; $k < 10; $k++) {
    for ($j = 0; $j < 1000; $j++) {
        $val = '';
        for ($i = 0; $i < 1000; $i++) {
            $val .= '("' . generate_random_string() . '", ' . rand (0, 10000) . ', "' . ($keys[rand(0, 9)]) . '"),';
        }
        $val = rtrim($val, ',');
        $pdo->query('INSERT INTO jan_char VALUES ' . $val);
    }
    echo "\n" . ($k + 1) . ' millon(s) rows inserted.';
}

Pour les tables int , le bit ($ keys [rand (0, 9)]) a été remplacé par seulement rand (0, 9) et pour les tables varchar , j’ai utilisé des noms d’états américains complets, sans les couper ni les étendre à 6 caractères. generate_random_string () génère une chaîne aléatoire de 10 caractères.

Ensuite, j'ai lancé MySQL:

  • SET SESSION query_cache_type = 0;
  • Pour la table jan_int :
    • SELECT count (*) FROM jan_int WHERE myindex = 5;
    • SELECT BENCHMARK (1000000000, (nombre SELECT (*) DE jan_int WHERE myindex = 5));
  • Pour les autres tables, comme ci-dessus, avec myindex = 'califo' pour les tables char et myindex = 'california' pour varchar tables.

Temps de la requête BENCHMARK sur chaque table:

  • jan_int: 21.30 sec
  • jan_int_index: 18.79 sec
  • jan_char: 21.70 secondes
  • jan_char_index: 18.85 sec
  • jan_varchar: 21.76 secondes
  • jan_varchar_index: 18.86 secondes

En ce qui concerne la table & amp; tailles d'index, voici la sortie de affiche le statut de la table à partir de janperformancetest; (avec quelques colonnes non affichées):

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Name              | Engine | Version | Row_format | Rows    | Avg_row_length | Data_length | Max_data_length | Index_length | Data_free | Auto_increment | Collation              |
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| jan_int           | InnoDB |      10 | Dynamic    | 9739094 |             43 |   422510592 |               0 |            0 |   4194304 |           NULL | utf8mb4_unicode_520_ci |  
| jan_int_index     | InnoDB |      10 | Dynamic    | 9740329 |             43 |   420413440 |               0 |    132857856 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_char          | InnoDB |      10 | Dynamic    | 9726613 |             51 |   500170752 |               0 |            0 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_char_index    | InnoDB |      10 | Dynamic    | 9719059 |             52 |   513802240 |               0 |    202342400 |   5242880 |           NULL | utf8mb4_unicode_520_ci |  
| jan_varchar       | InnoDB |      10 | Dynamic    | 9722049 |             53 |   521142272 |               0 |            0 |   7340032 |           NULL | utf8mb4_unicode_520_ci |   
| jan_varchar_index | InnoDB |      10 | Dynamic    | 9738381 |             49 |   486539264 |               0 |    202375168 |   7340032 |           NULL | utf8mb4_unicode_520_ci | 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

Ma conclusion est qu'il n'y a pas de différence de performances pour ce cas d'utilisation particulier.

Pour les codes abrégés, il n'y a probablement pas de différence. Cela est d'autant plus vrai que la table contenant ces codes est susceptible d'être très petite (quelques milliers de lignes au plus) et de ne pas changer souvent (quand est la dernière fois que nous avons ajouté un nouvel État américain).

Pour les tables plus volumineuses avec une variation plus importante entre les clés, cela peut être dangereux. Pensez par exemple à utiliser une adresse électronique / un nom d'utilisateur à partir d'une table d'utilisateurs. Que se passe-t-il lorsque vous avez quelques millions d'utilisateurs et que certains d'entre eux ont des noms longs ou des adresses électroniques? Maintenant, chaque fois que vous avez besoin de joindre cette table avec cette clé, cela devient beaucoup plus coûteux.

Comme pour la clé primaire, ce qui rend physiquement une ligne unique doit être déterminé comme clé primaire.

Pour une référence en tant que clé étrangère, utiliser un entier auto-incrémenté en tant que substitution est une bonne idée pour deux raisons principales.
 - Premièrement, il y a généralement moins de frais généraux dans la jointure.
 - Deuxièmement, si vous devez mettre à jour la table qui contient l’unique varchar, la mise à jour doit ensuite être redirigée vers toutes les tables enfants et les mettre à jour, ainsi que les index. table principale et ses index.

Le problème avec l'utilisation de la mère porteuse est que vous pouvez éventuellement permettre de changer le sens de la mère porteuse:

ex.
id value
1 A
2 B
3 C

Update 3 to D
id value
1 A
2 B
3 D

Update 2 to C
id value
1 A
2 C
3 D

Update 3 to B
id value
1 A
2 C
3 B

Tout dépend de ce dont vous avez vraiment besoin dans votre structure et de ce qui signifie le plus.

Cas courants dans lesquels un substitut AUTO_INCREMENT est blessé:

Un modèle de schéma commun est un mappage plusieurs à plusieurs :

.
CREATE TABLE map (
    id ... AUTO_INCREMENT,
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(id),
    UNIQUE(foo_id, bar_id),
    INDEX(bar_id) );

Les performances de ce modèle sont bien meilleures, notamment avec InnoDB:

CREATE TABLE map (
    # No surrogate
    foo_id ...,
    bar_id ...,
    PRIMARY KEY(foo_id, bar_id),
    INDEX      (bar_id, foo_id) );

Pourquoi?

  • Les clés secondaires InnoDB nécessitent une recherche supplémentaire; en déplaçant la paire dans le PK, ce qui est évité dans une direction.
  • L'index secondaire est "couvrant", il n'a donc pas besoin de la recherche supplémentaire.
  • Cette table est plus petite en raison de l'élimination de id et d'un index.

Autre cas ( pays ):

country_id INT ...
-- versus
country_code CHAR(2) CHARACTER SET ascii

Trop souvent, le novice normalise country_code en un INT à 4 octets au lieu d'utiliser une chaîne "naturelle" à 2 octets presque inchangée. Plus rapide, plus petit, moins de jointures, plus lisible.

Chez HauteLook, beaucoup de nos tables ont été modifiées pour utiliser des clés naturelles. Nous avons constaté une augmentation réelle des performances. Comme vous l'avez mentionné, beaucoup de nos requêtes utilisent maintenant moins de jointures, ce qui les rend plus performantes. Nous utiliserons même une clé primaire composite si cela a du sens. Cela étant dit, certaines tables sont simplement plus faciles à utiliser si elles ont une clé de substitution.

De plus, si vous laissez des personnes écrire des interfaces dans votre base de données, une clé de substitution peut être utile. La tierce partie peut compter sur le fait que la clé de substitution ne changera que dans de très rares circonstances.

La question concerne MySQL, je dis donc qu'il y a une différence significative. S'il s'agissait d'Oracle (qui stocke les nombres sous forme de chaîne - oui, je ne pouvais pas le croire au début), alors il n'y a pas beaucoup de différence.

Le stockage dans la table n’est pas le problème mais la mise à jour et la référence à l’index l’est. Les requêtes impliquant la recherche d'un enregistrement en fonction de sa clé primaire sont fréquentes - vous voulez qu'elles se produisent le plus rapidement possible car elles se produisent souvent.

Il s’agit d’un processeur qui traite naturellement avec des entiers de 4 et 8 octets dans silicium . C’est VRAIMENT rapide de comparer deux nombres entiers - cela se produit en un ou deux cycles d’horloge.

Maintenant, regardez une chaîne - elle est composée de nombreux caractères (plus d'un octet par caractère ces jours-ci). La comparaison de deux chaînes pour la priorité ne peut pas être effectuée en un ou deux cycles. Au lieu de cela, les caractères des chaînes doivent être itérés jusqu'à ce qu'une différence soit trouvée. Je suis sûr qu'il existe des astuces pour accélérer les choses dans certaines bases de données, mais cela n'a aucune importance ici, car une comparaison int est effectuée naturellement et rapidement dans le silicium par le processeur.

Ma règle générale - chaque clé primaire doit être une INT auto-incrémentée, en particulier dans les applications OO utilisant un ORM (Hibernate, Datanucleus, etc.) où il existe de nombreuses relations entre les objets - elles sont généralement toujours implémentées sous forme de FK simple et La capacité de la base de données à résoudre ces problèmes rapidement est importante pour votre application " la réactivité.

J'ai été confronté au même dilemme. J'ai réalisé un schéma DW (Constellation Schema) avec 3 tables de faits, accidents de la route, véhicules en accidents et pertes en accidents en accidents. Les données incluent tous les accidents enregistrés au Royaume-Uni de 1979 à 2012, ainsi que 60 tableaux de dimensions. Au total, environ 20 millions de disques.

Relations entre les tables de faits:

+----------+          +---------+
| Accident |>--------<| Vehicle |
+-----v----+ 1      * +----v----+
     1|                    |1
      |    +----------+    |
      +---<| Casualty |>---+
         * +----------+ *

RDMS: MySQL 5.6

Nativement, l’indice d’accident est un varchar (chiffres et lettres), composé de 15 chiffres. J'ai essayé de ne pas avoir de clé de substitution, une fois que les index des accidents ne changeraient jamais. Sur un ordinateur i7 (8 cœurs), le DW est devenu trop lent pour interroger après 12 millions d’enregistrements de charge en fonction des dimensions. Après beaucoup de travail et l'ajout de clés de substitution bigint, j'ai obtenu un gain de performances de vitesse moyen de 20%. Encore à faible gain de performance, mais essai valable. Je travaille dans le réglage et la mise en cluster de MySQL.

Je ne suis pas sûr des implications en termes de performances, mais il semble qu'un compromis possible, du moins pendant le développement, consisterait à inclure à la fois le "entier de substitution", auto-incrémenté, de type entier. clé, ainsi que votre objectif, unique, "naturel" clé. Cela vous donnerait la possibilité d’évaluer les performances, ainsi que d’autres problèmes éventuels, notamment la possibilité de modification des clés naturelles.

Comme d'habitude, il n'y a pas de réponse générale. 'Ça dépend!' et je ne suis pas facétieux. Ma compréhension de la question initiale concernait les clés de petites tables, telles que Pays (identifiant entier ou code de caractère / varchar), qui sont une clé étrangère d’une table potentiellement énorme, telle que la table adresse / contact.

Il existe deux scénarios lorsque vous souhaitez récupérer des données à partir de la base de données. Tout d’abord, il s’agit d’une requête de type liste / recherche dans laquelle vous souhaitez répertorier tous les contacts avec des codes ou noms d’État et de pays (les identifiants ne vous aideront pas et nécessiteront donc une recherche). L’autre est un scénario d’obtention sur une clé primaire qui affiche un enregistrement de contact unique dans lequel le nom de l’État, du pays doit être affiché.

Pour ces derniers, peu importe sur quoi est basé le FK puisque nous rassemblons des tables pour un seul enregistrement ou pour quelques enregistrements et pour les lectures clés. Le premier scénario (recherche ou liste) peut être affecté par notre choix. Puisqu'il est nécessaire de montrer pays (au moins un code reconnaissable et peut-être même la recherche inclut un code de pays), ne pas avoir à joindre une autre table via une clé de substitution peut potentiellement (je suis juste prudent ici parce que je n'ai pas réellement testé ceci, mais cela semble hautement probable) améliore les performances; malgré le fait que cela aide certainement à la recherche.

Les codes étant de petite taille - pas plus de 3 caractères généralement pour le pays et l’état, vous pouvez utiliser les clés naturelles comme clés étrangères dans ce scénario.

L'autre scénario dans lequel les clés dépendent de valeurs varchar plus longues et peut-être de tables plus volumineuses; la clé de substitution a probablement l'avantage.

Permettez-moi de dire oui, il y a bien une différence, compte tenu de l'étendue des performances (définition standard):

1- Utiliser l'application de substitution int est plus rapide dans l'application, car vous n'avez pas besoin d'utiliser ToUpper (), ToLower (), ToUpperInvarient () ou ToLowerInvarient () dans votre code ou dans votre requête. Ces 4 fonctions ont des performances différentes . Voir les règles de performance de Microsoft à ce sujet. (performance de l'application)

2- L'utilisation de la substitution int garantit de ne pas modifier la clé dans le temps. Même les codes de pays peuvent changer, voir Wikipedia comment les codes ISO ont changé au fil du temps. Cela prendrait beaucoup de temps pour changer la clé primaire des sous-arbres. (performances de la maintenance des données)

3- Il semble y avoir des problèmes avec les solutions ORM, tels que NHibernate lorsque PK / FK n’est pas int. (performance du développeur)

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