web-dev-qa-db-fra.com

Mysql int vs varchar comme clé primaire (InnoDB Storage Engine?

Je construis une application web (système de gestion de projet) et je me pose des questions à ce sujet en matière de performances.

J'ai une table Issues et à l'intérieur il y a 12 clés étrangères reliant à diverses autres tables. de ceux-ci, 8 d'entre eux, je devrais rejoindre pour obtenir le champ de titre des autres tables afin que l'enregistrement ait un sens dans une application Web, mais signifie ensuite faire 8 jointures, ce qui semble vraiment excessif, d'autant plus que je ne fais qu'arriver 1 champ pour chacune de ces jointures.

Maintenant, on m'a également dit d'utiliser une clé primaire à incrémentation automatique (sauf si le partage est un problème auquel cas je devrais utiliser un GUID) pour des raisons de permanence, mais à quel point est-il mauvais d'utiliser une varchar (longueur maximale 32) en termes de performances? Je veux dire que la plupart de ces tableaux n'auront probablement pas de nombreux enregistrements (la plupart d'entre eux devraient avoir moins de 20 ans). De plus, si j'utilise le titre comme clé primaire, je n'aurai pas à faire de jointures 95% du temps, donc pour 95% du sql, j'aurais même un impact sur les performances (je pense). Le seul inconvénient auquel je peux penser est que j'ai une utilisation plus importante de l'espace disque (mais un jour, c'est vraiment un gros problème).

La raison pour laquelle j'utilise des tables de recherche pour beaucoup de ces choses au lieu des énumérations est parce que j'ai besoin que toutes ces valeurs soient configurables par l'utilisateur final via l'application elle-même.

Quels sont les inconvénients de l'utilisation d'un varchar comme clé primaire pour une table qui n'est pas exempte d'avoir de nombreux enregistrements?

MISE À JOUR - Quelques tests

J'ai donc décidé de faire des tests de base sur ce genre de choses. J'ai 100 000 enregistrements et ce sont les requêtes de base:

Requête VARCHAR FK de base

SELECT i.id, i.key, i.title, i.reporterUserUsername, i.assignedUserUsername, i.projectTitle, 
i.ProjectComponentTitle, i.affectedProjectVersionTitle, i.originalFixedProjectVersionTitle, 
i.fixedProjectVersionTitle, i.durationEstimate, i.storyPoints, i.dueDate, 
i.issueSecurityLevelId, i.creatorUserUsername, i.createdTimestamp, 
i.updatedTimestamp, i.issueTypeId, i.issueStatusId
FROM ProjectManagement.Issues i

Base INT FK Query

SELECT i.id, i.key, i.title, ru.username as reporterUserUsername, 
au.username as assignedUserUsername, p.title as projectTitle, 
pc.title as ProjectComponentTitle, pva.title as affectedProjectVersionTitle, 
pvo.title as originalFixedProjectVersionTitle, pvf.title as fixedProjectVersionTitle, 
i.durationEstimate, i.storyPoints, i.dueDate, isl.title as issueSecurityLevelId, 
cu.username as creatorUserUsername, i.createdTimestamp, i.updatedTimestamp, 
it.title as issueTypeId, is.title as issueStatusId
FROM ProjectManagement2.Issues i
INNER JOIN ProjectManagement2.IssueTypes `it` ON it.id = i.issueTypeId
INNER JOIN ProjectManagement2.IssueStatuses `is` ON is.id = i.issueStatusId
INNER JOIN ProjectManagement2.Users `ru` ON ru.id = i.reporterUserId
INNER JOIN ProjectManagement2.Users `au` ON au.id = i.assignedUserId
INNER JOIN ProjectManagement2.Users `cu` ON cu.id = i.creatorUserId
INNER JOIN ProjectManagement2.Projects `p` ON p.id = i.projectId
INNER JOIN ProjectManagement2.`ProjectComponents` `pc` ON pc.id = i.projectComponentId
INNER JOIN ProjectManagement2.ProjectVersions `pva` ON pva.id = i.affectedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvo` ON pvo.id = i.originalFixedProjectVersionId
INNER JOIN ProjectManagement2.ProjectVersions `pvf` ON pvf.id = i.fixedProjectVersionId
INNER JOIN ProjectManagement2.IssueSecurityLevels isl ON isl.id = i.issueSecurityLevelId

J'ai également exécuté ces requêtes avec les ajouts suivants:

  • Sélectionnez un élément spécifique (où i.key = 43298)
  • Grouper par i.id
  • Trier par (it.title pour int FK, i.issueTypeId pour varchar FK)
  • Limite (50000, 100)
  • Regrouper et limiter ensemble
  • Grouper, ordonner et limiter ensemble

Les résultats pour ceux-ci où:

TYPE DE REQUÊTE: VARCHAR FK TIME/INT FK TIME


Requête de base: ~ 4 ms/~ 52 ms

Sélectionnez un élément spécifique: ~ 140 ms/~ 250 ms

Grouper par i.id: ~ 4ms/~ 2.8sec

Trier par: ~ 231ms/~ 2sec

Limite: ~ 67 ms/~ 343 ms

Grouper et limiter ensemble: ~ 504 ms/~ 2 s

Grouper, ordonner et limiter ensemble: ~ 504 ms/~ 2,3 s

Maintenant, je ne sais pas quelle configuration je pourrais faire pour rendre l'un ou l'autre (ou les deux) plus rapide, mais il semble que le VARCHAR FK voit plus rapidement dans les requêtes de données (parfois beaucoup plus rapide).

Je suppose que je dois choisir si cette amélioration de la vitesse vaut la taille supplémentaire des données/index.

13
ryanzec

Je respecte les règles suivantes pour les clés primaires:

a) Ne devrait pas avoir de sens commercial - ils devraient être totalement indépendants de l'application que vous développez, donc je préfère les nombres numériques générés automatiquement. Cependant, si vous avez besoin de colonnes supplémentaires pour être uniques, créez des index uniques pour les prendre en charge.

b) Devrait fonctionner dans les jointures - rejoindre varchars vs entiers est environ 2x à 3x plus lent à mesure que la longueur de la clé primaire augmente, donc vous voulez avoir vos clés sous forme d'entiers. Étant donné que tous les systèmes informatiques sont binaires, je soupçonne que son coz la chaîne est changé en binaire puis par rapport aux autres qui est très lent

c) Utilisez le plus petit type de données possible - si vous vous attendez à ce que votre table contienne très peu de colonnes, dites 52 États américains, alors utilisez le plus petit type possible, peut-être un CHAR (2) pour le code à 2 chiffres, mais j'irais quand même pour un minuscule (128) pour la colonne vs un gros int qui peut aller jusqu'à 2 milliards

Vous aurez également du mal à mettre en cascade vos modifications des clés primaires vers les autres tables si, par exemple, le nom du projet change (ce qui n'est pas rare)

Optez pour des nombres entiers à incrémentation automatique séquentielle pour vos clés primaires et gagnez en efficacité intégrée que les systèmes de base de données fournissent avec la prise en charge des modifications futures

Dans vos tests, vous ne comparez pas la différence de performances entre varchar et les clés int, mais plutôt le coût de plusieurs jointures. Il n'est pas surprenant que l'interrogation d'une table soit plus rapide que la jonction de plusieurs tables.
Un inconvénient de la clé primaire varchar est l'augmentation de la taille de l'index comme atxdba l'a souligné. Même si votre table de recherche n'a pas d'autres index que PK (ce qui est peu probable, mais possible), chaque table qui référence la recherche aura un index sur cette colonne.
Une autre mauvaise chose à propos des clés primaires naturelles, c'est que leur valeur peut changer, ce qui provoque de nombreuses mises à jour en cascade. Tous les RDMS, par exemple Oracle, ne vous permettent même pas d'avoir on update cascade. En général, la modification de la valeur de la clé primaire est considérée comme une très mauvaise pratique. Je ne veux pas dire que les clés primaires naturelles sont toujours mauvaises; si les valeurs de recherche sont petites et ne changent jamais, je pense que cela peut être acceptable.

Une option que vous voudrez peut-être envisager est d'implémenter la vue matérialisée. Mysql ne le prend pas directement en charge, mais vous pouvez obtenir la fonctionnalité souhaitée avec des déclencheurs sur les tables sous-jacentes. Vous aurez donc une table qui a tout ce dont vous avez besoin pour afficher. De plus, si les performances sont acceptables, ne vous débattez pas avec le problème qui n'existe pas pour le moment.

6
a1ex07

Le plus gros inconvénient est la répétition du PK. Vous avez souligné une augmentation de l'utilisation de l'espace disque, mais pour être clair, l'augmentation de la taille de l'index est votre plus grande préoccupation. Étant donné que innodb est un index clusterisé, chaque index secondaire stocke en interne une copie du PK qu'il utilise pour finalement trouver les enregistrements correspondants.

Vous dites que les tables sont censées être "petites" (en effet, 20 lignes sont très petites). Si vous en avez assez RAM pour définir la taille innodb_buffer_pool_size égale à

select sum(data_length+index_length) from information_schema.tables where engine='innodb';

Ensuite, faites-le et vous serez probablement bien assis. En règle générale, vous devez laisser au moins 30% à 40% de la mémoire totale du système pour les autres surcharges mysql et la mise en cache. Et cela suppose qu'il s'agit d'un serveur de base de données dédié. Si d'autres choses s'exécutent sur le système, vous devrez également prendre en compte leurs besoins.

3
atxdba

En plus de la réponse @atxdba - qui vous a expliqué pourquoi l'utilisation du numérique serait préférable pour l'espace disque, je voulais ajouter deux points:

  1. Si votre table Issues est basée sur VARCHAR FK, et supposons que vous ayez 20 petits VARCHAR (32) FK, votre enregistrement peut atteindre une longueur de 20x32 octets, tandis que, comme vous l'avez mentionné, les autres tables sont des tables de recherche, donc INT FK pourrait être TINYINT FK, ce qui fait pour 20 champs un enregistrement de 20 octets. Je sais que pour plusieurs centaines d'enregistrements, cela ne changera pas grand-chose, mais lorsque vous atteindrez plusieurs millions, je suppose que vous apprécierez l'économie d'espace

  2. Pour le problème de vitesse, j'envisagerais d'utiliser des index de couverture, car il semble que pour cette requête, vous ne récupérez pas autant de données des tables de recherche que j'irais pour couvrir l'index et refaire le test que vous avez fourni avec VARCHAR FK/W/COVERING INDEX ET INT FK régulier.

J'espère que cela pourrait aider,

1
Spredzy