Le contexte
Je suis en train de concevoir une base de données (sur PostgreSQL 9.6) qui stockera les données d'une application distribuée. En raison de la nature distribuée de l'application, je ne peux pas utiliser d'entiers à incrémentation automatique ( SERIAL
) comme clé primaire en raison de conditions de concurrence potentielles.
La solution naturelle consiste à utiliser un UUID ou un identifiant globalement unique. Postgres est livré avec un intégré de UUID
type , ce qui est un ajustement parfait.
Le problème que j'ai avec UUID est lié au débogage: c'est une chaîne non conviviale. L'identifiant ff53e96d-5fd7-4450-bc99-111b91875ec5
ne me dit rien, alors que ACC-f8kJd9xKCd
, même s'il n'est pas garanti qu'il soit unique, il me dit que j'ai affaire à un ACC
objet.
Du point de vue de la programmation, il est courant de déboguer des requêtes d'application concernant plusieurs objets différents. Supposons que le programmeur recherche à tort un ACC
objet (compte) auORD
table (commande). Avec un identifiant lisible par l'homme, le programmeur identifie instantanément le problème, tout en utilisant des UUID, il passait un peu de temps à découvrir ce qui n'allait pas.
Je n'ai pas besoin de l'unicité "garantie" des UUID; Je fais besoin de place pour générer des clés sans conflits, mais UUID est surpuissant. De plus, dans le pire des cas, ce ne serait pas la fin du monde si une collision se produisait (la base de données la rejette et l'application peut récupérer). Ainsi, les compromis envisagés, un identifiant plus petit mais convivial serait la solution idéale pour mon cas d'utilisation.
Identification des objets d'application
L'identifiant que j'ai trouvé a le format suivant:, {domain}-{string}
où {domain}
est remplacé par le domaine d'objet (compte, commande, produit) et {string}
est une chaîne générée aléatoirement. Dans certains cas, il peut même être judicieux d'insérer un {sub-domain}
avant la chaîne aléatoire. Ignorons la longueur {domain}
et {string}
dans le but de garantir l'unicité.
Le format peut avoir une taille fixe s'il améliore les performances d'indexation / interrogation.
Le problème
Sachant que:
- Je veux avoir des clés primaires avec un format comme
ACC-f8kJd9xKCd
. - Ces clés primaires feront partie de plusieurs tables.
- Toutes ces clés seront utilisées sur plusieurs jointures / relations, sur une base de données 6NF.
- La plupart des tables auront une taille moyenne à grande (en moyenne ~ 1 M de lignes; les plus grandes avec ~ 100 M lignes).
Concernant les performances, quelle est la meilleure façon de stocker cette clé?
Vous trouverez ci-dessous quatre solutions possibles, mais comme je n'ai que peu d'expérience avec les bases de données, je ne sais pas laquelle (le cas échéant) est la meilleure.
Solutions envisagées
1. Stocker sous forme de chaîne ( VARCHAR
)
(Postgres ne fait aucune différence entre CHAR(n)
et VARCHAR(n)
, donc j'ignore CHAR
).
Après quelques recherches, j'ai découvert que la comparaison de chaînes avec VARCHAR
, en particulier sur les opérations de jointure, est plus lente que l'utilisation INTEGER
. C'est logique, mais est-ce quelque chose dont je dois m'inquiéter à cette échelle?
2. Stocker en binaire ( bytea
)
Contrairement à Postgres, MySQL n'a pas de UUID
type natif . Il existe plusieurs articles expliquant comment stocker un UUID à l'aide d'un BINARY
champ de 16 octets , au lieu d'un champ de 36 octets VARCHAR
. Ces messages m'ont donné l'idée de stocker la clé au format binaire ( bytea
sur Postgres).
Cela économise de la taille, mais je suis plus préoccupé par les performances. J'ai eu peu de chance de trouver une explication sur laquelle la comparaison est plus rapide: binaire ou chaîne. Je pense que les comparaisons binaires sont plus rapides. S'ils le sont, alors bytea
c'est probablement mieux que VARCHAR
, même si le programmeur doit maintenant encoder / décoder les données à chaque fois.
Je peux me tromper, mais je pense les deux bytea
et VARCHAR
comparerai (l'égalité) octet par octet (ou caractère par caractère). Existe-t-il un moyen de "sauter" cette comparaison étape par étape et de comparer simplement "le tout"? (Je ne pense pas, mais cela ne coûte pas de vérifier).
Je pense que le stockage bytea
est la meilleure solution, mais je me demande s'il y a d'autres alternatives que j'ignore. De plus, la même préoccupation que j'ai exprimée à propos de la solution 1 est vraie: les frais généraux sur les comparaisons sont-ils suffisants pour que je me préoccupe?
"Des solutions créatives
J'ai trouvé deux solutions très «créatives» qui pourraient fonctionner, je ne sais pas dans quelle mesure (c'est-à-dire si j'aurais du mal à les mettre à l'échelle sur plus de quelques milliers de lignes dans un tableau).
3. Conserver comme UUID
mais avec une "étiquette" attachée
La principale raison de ne pas utiliser les UUID est que les programmeurs puissent mieux déboguer l'application. Mais que faire si nous pouvons utiliser les deux: la base de données stocke toutes les clés UUID
uniquement en s, mais elle encapsule l'objet avant / après les requêtes.
Par exemple, le programmeur le demande ACC-{UUID}
, la base de données ignore la ACC-
pièce, récupère les résultats et les renvoie tous sous la forme {domain}-{UUID}
.
Peut-être que cela serait possible avec un certain piratage avec des procédures ou fonctions stockées, mais certaines questions viennent à l'esprit:
- Est-ce (supprimer / ajouter le domaine à chaque requête) un surcoût substantiel?
- Est-ce seulement possible?
Je n'ai jamais utilisé de procédures ou de fonctions stockées auparavant, donc je ne sais pas si c'est même possible. Quelqu'un peut-il faire la lumière? Si je peux ajouter une couche transparente entre le programmeur et les données stockées, cela semble une solution parfaite.
4. (Mon préféré) Stocker en IPv6 cidr
Oui, vous l'avez bien lu. Il s'avère que le format d'adresse IPv6 résout parfaitement mon problème .
- Je peux ajouter des domaines et des sous-domaines dans les premiers octets et utiliser les autres comme chaîne aléatoire.
- Les chances de collision sont OK. (Je n'utiliserais pas 2 ^ 128 cependant, mais c'est toujours OK.)
- Les comparaisons d'égalité sont (espérons-le) optimisées, donc je pourrais obtenir de meilleures performances que de simplement utiliser
bytea
. - Je peux en fait effectuer des comparaisons intéressantes, comme
contains
, selon la façon dont les domaines et leur hiérarchie sont représentés.
Par exemple, supposons que j'utilise du code 0000
pour représenter le domaine "produits". La clé 0000:0db8:85a3:0000:0000:8a2e:0370:7334
représenterait le produit 0db8:85a3:0000:0000:8a2e:0370:7334
.
La question principale ici est: par rapport à bytea
, y a-t-il un avantage ou un inconvénient principal à utiliser cidr
le type de données?
varchar
parmi beaucoup d'autres problèmes. Je ne connaissais pas les domaines de pg, ce qui est formidable à apprendre. Je vois des domaines utilisés pour valider si une requête donnée utilise le bon objet, mais cela dépendrait toujours d'un index non entier. Je ne sais pas s'il existe un moyen "sécurisé" d'utiliser serial
ici (sans une seule étape de verrouillage).
varchar
. Envisagez d'en faire un FK
integer
type et ajoutez-y une table de recherche. De cette façon, vous pouvez avoir à la fois une lisibilité humaine et vous protégerez votre composite PK
des anomalies d'insertion / mise à jour (en mettant un domaine inexistant).
text
est préférable à varchar
. Regardez depesz.com/2010/03/02/charx-vs-varcharx-vs-varchar-vs-text et postgresql.org/docs/current/static/datatype-character.html
ACC-f8kJd9xKCd
. ”← Cela semble être un travail pour la bonne vieille CLÉ PRIMAIRE composite .