Index: performances entières vs chaînes si le nombre de nœuds est le même

26

Je développe une application en Ruby on Rails avec la base de données PostgreSQL (9.4). Pour mon cas d'utilisation, les colonnes dans les tableaux seront recherchées très fréquemment, car tout le point de l'application recherche des attributs très spécifiques sur un modèle.

Je décide actuellement d'utiliser un integertype ou simplement d'utiliser un type de chaîne typique (par exemple character varying(255), qui est la valeur par défaut dans Rails ) pour les colonnes, car je ne sais pas quelle sera la différence de performances sur l'index.

Ces colonnes sont des énumérations . Ils ont une taille fixe pour le nombre de valeurs possibles qu'ils peuvent avoir. La plupart des longueurs d'énumération ne dépassent pas 5, ce qui signifie que l'indice serait plus ou moins fixe pendant toute la durée de vie de l'application ; ainsi, les indices d'entier et de chaîne seraient identiques en nombre de nœuds.

Cependant, la chaîne qui serait indexée pourrait avoir une longueur d'environ 20 caractères, ce qui en mémoire est à peu près 5 fois celui de l'entier (si un entier est de 4 octets et que les chaînes sont en ASCII pur à 1 octet par caractère, cela est valable). Je ne sais pas comment les moteurs de base de données indexent les recherches, mais s'il a besoin de "scanner" la chaîne jusqu'à ce qu'elle corresponde exactement , cela signifie essentiellement que la recherche de chaîne serait 5 fois plus lente qu'une recherche entière; le "scan" jusqu'à ce que la correspondance pour la recherche entière soit de 4 octets au lieu de 20. C'est ce que j'imagine:

La valeur de recherche est (entier) 4:

numérisation ............................ TROUVE | obtention d'enregistrements ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

La valeur de recherche est (chaîne) "some_val" (8 octets):

balayage................................................. .................................... TROUVE | obtention d'enregistrements ... | BYTE_1 | BYTE_2 | BYTE_3 | BYTE_4 | BYTE_5 | BYTE_6 | BYTE_7 | BYTE_8 | ... |

J'espère que cela a du sens. Fondamentalement, parce que l'entier occupe moins d'espace, il peut être "mis en correspondance" plus rapidement que son homologue de chaîne. C'est peut-être une supposition complètement fausse, mais je ne suis pas un expert, c'est pourquoi je vous pose la question! Je suppose que cette réponse que je viens de trouver semble soutenir mon hypothèse, mais je veux en être sûr.

Le nombre de valeurs possibles dans la colonne ne changerait pas en utilisant l'une ou l'autre, donc l'index lui-même ne changerait pas (sauf si j'ai ajouté une nouvelle valeur à l'énumération). Dans ce cas, y aurait-il une différence de performances dans l'utilisation de integerou varchar(255), ou l'utilisation d'un type entier est-elle plus logique?


La raison pour laquelle je demande est que le enumtype de Rails mappe les entiers aux clés de chaîne, mais ils ne sont pas destinés à être des colonnes accessibles aux utilisateurs. Essentiellement, vous ne pouvez pas vérifier que la valeur d'énumération est valide, car une valeur non valide provoquera un ArgumentErroravant l'exécution de toutes les validations. L'utilisation d'un stringtype permettrait des validations, mais s'il y a un coût de performance, je préfère simplement contourner le problème de validation.

Chris Cirefice
la source

Réponses:

32

Réponse courte: integerest plus rapide que varcharou textdans tous les aspects. Peu importe pour les petites tables et / ou les touches courtes. La différence augmente avec la longueur des clés et le nombre de lignes.

chaîne ... 20 caractères de long, ce qui en mémoire est à peu près 5 fois celui de l'entier (si un entier est de 4 octets, et que les chaînes sont en ASCII pur à 1 octet par caractère, alors cela vaut)

Pour être précis, les types de caractères ( textou varchar) occupent exactement 21 octets pour 20 caractères ASCII sur le disque et 23 octets dans la RAM. Évaluation détaillée:

Également important: les COLLATIONrègles peuvent rendre le tri des données de caractères plus cher - contrairement aux types de données numériques:

La taille de l' indice est probablement responsable de la part du lion de la différence de performance dans la plupart des cas. Considérez la surcharge par tuple d'index (fondamentalement la même que pour une table): 4 octets pour le pointeur d'élément et 24 octets pour l'en-tête de tuple. Ainsi, le tuple d'index pour integerserait de 36 octets (dont 4 octets de remplissage d'alignement ) et pour varchar(20)avec 20 caractères ASCII, il serait de 52 octets (y compris également le remplissage). Détails:

Toute la théorie mise à part: il vaut mieux simplement tester:

Postgres 9.5 a introduit une optimisation pour le tri de longues chaînes de données de caractères (mot clé "clés abrégées" ). Mais un bogue dans certaines fonctions de la bibliothèque C sous Linux a forcé le projet à désactiver la fonctionnalité pour les classements non-C dans Postgres 9.5.2. Détails dans les notes de version.

Cependant, si vous utilisez réellement des enumtypes Postgres , la plupart de ces considérations ne sont pas pertinentes, car elles sont de integertoute façon implémentées avec des valeurs en interne. Le manuel:

Une enumvaleur occupe quatre octets sur le disque.

Mis à part: varchar(255)utilisé pour donner un sens aux premières versions de SQL Server, qui pouvaient utiliser un type de données plus efficace en interne jusqu'à la limite de 255 caractères. Mais la restriction de longueur impaire de 255 caractères n'a aucun impact spécial sur les performances de Postgres.

Erwin Brandstetter
la source
1
Il n'y a pas d'optimisation cachée dans SQL Server pour varchar(255)vs par exemple varchar(260). Il pourrait y avoir une telle chose avec SQL Server 6.x mais cela n'a pas été vrai depuis longtemps.
a_horse_with_no_name
@a_horse_with_no_name: merci, j'ai clarifié en conséquence.
Erwin Brandstetter
Désolé d'avoir mis si longtemps à l'accepter, j'ai été lent sur le développement de ce projet;)
Chris Cirefice
Cette réponse est-elle toujours valable pour Postgres 10, s'il vous plaît?
Matty
1
@Matty: toujours valide. Et je ne vois rien changer pour la page 11 non plus.
Erwin Brandstetter