Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
UUID Primary Keys sont "uniques dans l'univers" et présentent de nombreux avantages. Pensez à les utiliser dans votre prochain projet.
Nous connaissons tous le Integer Autoincrement Primary Key. Il fonctionne très bien, beaucoup de logiciels le font de cette façon. Il est rapide. Mais il a aussi ses limites.
Une alternative pour le Integer Autoincrement Primary Key est le UUID (Universally Unique IDentifier) Primary Key. Un UUID est un nombre de 128 bits. Il peut être représenté par une chaîne de caractères, par exemple :
05390f2b-48e8-45c7-8ae4-08272dc69122
ou une chaîne hexadécimale (comme ci-dessus avec le caractère tiret supprimé) :
05390f2b48e845c78ae408272dc69122
ou une valeur binaire.
UUIDs sont uniques, il n'y a pas de numérotation. Si vous générez un UUID sur votre ordinateur portable à Londres et que votre collègue à New York génère un UUID sur son ordinateur portable, alors les deux UUID sont uniques, ils ne peuvent jamais être identiques. Il existe plusieurs versions de UUID, ici nous allons utiliser la version 4, également appelée UUID "aléatoire". La probabilité d'une collision est presque nulle.
UUID Primary Keys sont-ils un bon choix ?
Je crois qu'il y a suffisamment de raisons, même pour les petits projets. Tout le monde connaît le problème lorsqu'il s'agit de fusionner des enregistrements de base de données ou des tables. Si vos tables de base de données utilisent Integer Autoincrement Primary Keys , cela peut être compliqué, mais avec UUIDs, c'est un jeu d'enfant.
Exemple n°1 : Blog avec plusieurs auteurs hors ligne
Supposons que vous ayez ce site web de CMS / blog et que vous ayez deux personnes, auteurs de billets de blog, travaillant chacune sur une copie locale, fonctionnant sur leur ordinateur portable. Alors comment transférer les nouveaux enregistrements dans le système de production ?
Si votre base de données utilise Integer Autoincrement Primary Keys , vous devez être extrêmement prudent. Vous le faites probablement en exportant certains enregistrements et en les important ensuite dans votre base de données de production. Vous ne pouvez pas importer les ID des enregistrements, les deux auteurs peuvent avoir des postes différents avec des ID identiques. Cela devient plus difficile lorsque les articles de blog font référence aux (nouveaux) enregistrements d'une table contenant les liens/références d'un article de blog. Il est probable que vous écriviez un script qui traite de ce problème.
Si nous utilisions UUID Primary Keys , nous exporterions simplement les nouveaux enregistrements des deux ordinateurs portables et les importerions dans la base de données de production. Pas de conflits, c'est facile.
Exemple n° 2 : Boutique en ligne avec des informations sur des produits préparés hors ligne
Supposons que vous ayez une boutique en ligne et que vous ayez des fournisseurs qui livrent leurs dossiers d'information sur les produits préparés avec UUID Primary Keys. Si votre boutique en ligne utilise déjà UUID Primary Keys , il est très facile de l'importer, sans conversion.
Avantages et inconvénients de l'utilisation d'un UUID comme clé primaire
Avantages :
- Vous pouvez générer des UUID partout
Cela signifie que vous pouvez créer des enregistrements sans vous connecter à une base de données - Les UUIDs sont uniques dans les tables, les bases de données, les systèmes
. Cela permet de fusionner facilement les enregistrements de différentes tables, bases de données - UUIDs rend la réplication plus facile
Inconvénients :
- Utilise plus de mémoire et d'espace disque, les tables d'index s'agrandissent
Mais qui s'en soucie aujourd'hui ? - Plus lent pour SELECT avec de grandes tables
Mais des optimisations sont possibles - Peut être beaucoup plus lent pour INSERT
Cela prend plus de temps pour recalculer l'index, peut être un problème sérieux
Mais vous pouvez ajouter une colonne DATETIME (microsecondes) "created_on" et l'utiliser pour le tri.
UUIDs empêchent la divulgation d'informations commerciales (secrets)
Un autre avantage important de l'utilisation d'une clé primaire UUID au lieu d'une clé Integer Autoincrement Primary Key est que vous pouvez moins vous soucier de la divulgation d'informations commerciales. Exemple : les contacts sont stockés dans une table de contacts. Si cette table a un Integer Autoincrement Primary Key , une vue d'édition pourrait exposer le user_id dans l'URL :
/contact/edit/24
Cela signifie que vous pouvez deviner le nombre de users d'un site web. Si vous utilisez une clé primaire UUID , alors l'identifiant user_id dans l'URL n'expose rien :
/contact/edit/5489b4d7abd9416f9ba31d48fbb0f5be
Bien sûr, il est toujours préférable de ne jamais exposer aucun type de Primary Key. Cela peut sembler trivial mais peut entraîner un travail supplémentaire important.
Exemple : considérons qu'une fiche de contact contient une clé étrangère pour une ville. La clé étrangère du contact fait référence à la ville Primary Key. Si nous ne voulons pas exposer la Primary Keys , nous pouvons ajouter une colonne Surrogate Key à chaque table et générer une Surrogate Key pour chaque enregistrement que nous créons. Lors de l'édition du contact, nous devons afficher une liste déroulante avec les villes disponibles. Nous ne voulons pas que la colonne Primary Keys des villes soit exposée et sélectionnons plutôt la colonne Surrogate Keys . Ensuite, lorsque le formulaire de contact est soumis, nous devons d'abord rechercher le Primary Key de la ville en utilisant la valeur Surrogate Key soumise. Une fois que nous avons récupéré la valeur Primary Key de la ville, nous pouvons mettre à jour le contact.
Python et UUIDs
Il est facile de générer des UUID dans les Python :
>>> import uuid
>>> uuid_obj = uuid.uuid4()
>>> uuid_obj
UUID('05390f2b-48e8-45c7-8ae4-08272dc69122')
>>> str(uuid_obj)
'05390f2b-48e8-45c7-8ae4-08272dc69122'
>>> uuid_obj.hex
'05390f2b48e845c78ae408272dc69122'
Nous pouvons également convertir l'hexadécimal UUID en une chaîne de caractères UUID :
>>> uuid_obj2 = uuid.UUID(hex='05390f2b48e845c78ae408272dc69122', version=4)
>>> str(uuid_obj2)
'05390f2b-48e8-45c7-8ae4-08272dc69122'
Ordre des inserts
Lorsque nous utilisons les clés primaires UUID , nous perdons l'ordre des insertions. Dans mon modèle, chaque table a une colonne MariaDb DATETIME "created_on", ce qui signifie que la résolution minimale est de 1 seconde. Lorsque l'ordre des insertions est important, nous pouvons augmenter la résolution de la colonne "created_on" à quelques microsecondes en utilisant DATETIME(6). Si vous avez vraiment besoin de l'ordre d'insertion, vous pouvez ajouter une colonne Autoincrement supplémentaire.
Performance
L'utilisation de UUIDs pour les clés primaires n'accélère pas vraiment les requêtes. Les requêtes SELECT fonctionnent plus lentement en raison de la comparaison de chaînes de caractères au lieu de la comparaison d'entiers. Les requêtes INSERT peuvent être vraiment lentes car pour chaque insertion, l'index doit être reconstruit avec des nombres aléatoires. Des optimisations sont possibles, mais elles présentent également des inconvénients, comme le fait que les UUID ne sont plus "aléatoires".
SQLAlchemy, MariaDb, UUIDs et Alembic
J'utilise SQLAlchemy et MariaDb, alors utilisons une valeur hexagonale de 32 caractères UUID. PostgreSQL prend déjà en charge les UUID prêts à l'emploi. Il y a une recette sur le site de SQLAlchemy : Backend-agnostic GUID Type", voir les liens ci-dessous. J'ai inclus cette recette (script), avec une petite modification, dans mon fichier models.py. Ensuite, j'écris mon modèle UUIDCity comme :
class UUIDCity(Base):
__tablename__ = 'uuid_city'
id = Column(GUID(), primary_key=True, default=uuid.uuid4)
...
Cela permet de générer un UUID pour un nouveau record. J'ai un peu modifié le script pour m'assurer qu'il renvoie une valeur de chaîne et non un objet GUID. Cela rend son utilisation beaucoup plus facile dans les requêtes. (Mais je risque de perdre certains avantages de SQLAlchemy D)
...
def process_result_value(self, value, dialect):
if value is None:
return value
else:
# 20200328/PPM commented the next two lines to return the db id as string
# if not isinstance(value, uuid.UUID):
# value = uuid.UUID(value)
return value
Il y a un problème de Alembic ici. Lorsque vous exécutez Alembic pour le modèle UUIDCity :
alembic revision --autogenerate -m "some version text"
Alembic génère un fichier de versions avec :
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('uuid_demo_city',
sa.Column('id', shared.models.GUID(), nullable=False),
sa.Column('name', sa.String(length=250), server_default='', nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_uuid_demo_city_name'), 'uuid_demo_city', ['name'], unique=False)
J'ai un dossier appelé "shared" et dans ce dossier se trouve le fichier models.py. Cela fera échouer la commande de mise à niveau. Le résultat de la commande :
alembic upgrade head
est :
File "...", line 17, in upgrade
sa.Column('id', shared.models.GUID(), nullable=False),
NameError: name 'shared' is not defined
C'est un peu déroutant, mais cela tient au fait qu'il n'y a pas de type de données shared.models.GUID(). Vous pouvez modifier env.py et script.py.mako. Vous trouverez quelques informations dans les liens ci-dessous. Je n'ai pas encore fait cela. Pour l'instant, j'ai édité le fichier des versions et j'ai modifié :
sa.Column('id', shared.models.GUID(), nullable=False),
en :
sa.Column('id', sa.CHAR(32), nullable=False),
Maintenant, la commande de mise à jour fonctionne sans erreur. Nous devons également suivre cette procédure pour les clés étrangères. Bien sûr, il est préférable de modifier les scripts mentionnés ci-dessus, mais cette modification n'a lieu qu'une seule fois lors de la création du modèle.
Résumé
Comment en suis-je venu à écrire ce billet ? Je pense que je cherchais des moyens de générer des codes aléatoires uniques. Dans Python , l'utilisation de UUIDs est facile. J'ai peut-être tapé le code aléatoire unique + MySQL. Ensuite, vous avez également tapé le code UUID Primary Keys. J'ai déjà lu des articles à ce sujet dans le passé, mais cette fois-ci, j'ai pris le temps de faire des recherches.
Si vous utilisez une base de données avec Integer Autoincrement Primary Keys , vaut-il la peine de passer à l'utilisation de UUID Primary Keys ? Je ne pense pas. Cela peut prendre beaucoup de temps et vous pouvez vous attirer beaucoup d'ennuis.
Mais pour les nouvelles bases de données, même très petites, je recommande d'utiliser UUID Primary Keys. Vous ne savez jamais si vous vous retrouverez un jour dans une situation où vous devrez fusionner des tables, importer des enregistrements avec des clés UUID , faire travailler des personnes hors ligne avec votre base de données.
Bien sûr, il y a des exceptions. Supposons que vous échantillonniez des images de webcam et que vous stockiez leurs références dans un enregistrement de la base de données. Dans ce cas, vous voulez une performance maximale de INSERT , alors n'utilisez pas UUID Primary Keys ici !
Un site web performant avec une grande base de données ne doit généralement pas souffrir d'un ralentissement UUID Primary Keys. SELECT est seulement un peu plus lent et, s'il est conçu correctement, la majorité des requêtes (SELECT) sont servies à partir du cache.
Pour ce site web de CMS/ blog, j'ai décidé de convertir (très) lentement les Integer Autoincrement existantes en UUID Primary Keys.
Liens / crédits
Alembic: How to migrate custom type in a model?
https://stackoverflow.com/questions/15668115/alembic-how-to-migrate-custom-type-in-a-model
Backend-agnostic GUID Type
https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type
Best UUID data type?
https://www.memsql.com/forum/t/best-uuid-data-type/182
Configuring MySQL to allow generation of Primary Key values
https://stackoverflow.com/questions/21669815/configuring-mysql-to-allow-generation-of-primary-key-values
GUID/UUID Performance
https://mariadb.com/kb/en/guiduuid-performance/
How can I bind a list to a parameter in a custom query in sqlalchemy?
https://stackoverflow.com/questions/13190392/how-can-i-bind-a-list-to-a-parameter-in-a-custom-query-in-sqlalchemy
How can I use UUIDs in SQLAlchemy?
https://stackoverflow.com/questions/183042/how-can-i-use-uuids-in-sqlalchemy
Microseconds in MariaDB
https://mariadb.com/kb/en/microseconds-in-mariadb/
UUIDs are Popular, but Bad for Performance — Let’s Discuss
https://www.percona.com/blog/2019/11/22/uuids-are-popular-but-bad-for-performance-lets-discuss/
En savoir plus...
Alembic MariaDB SQLAlchemy UUID
Laissez un commentaire
Commentez anonymement ou connectez-vous pour commenter.
Commentaires (1)
Laissez une réponse
Répondez de manière anonyme ou connectez-vous pour répondre.
Thanks for your post! I finally got my challenge solved by also commenting the 2 lines in the GUID class. It was a pain to get UUID objects back instead of plain UUID strings.
Récent
- Graphique de séries temporelles avec Flask, Bootstrap et Chart.js
- Utiliser IPv6 avec Microk8s
- Utilisation de Ingress pour accéder à RabbitMQ sur un cluster Microk8s
- Galerie vidéo simple avec Flask, Jinja, Bootstrap et JQuery
- Planification de base des tâches avec APScheduler
- Un commutateur de base de données avec HAProxy et HAProxy Runtime API
Les plus consultés
- Utiliser PyInstaller et Cython pour créer un exécutable Python
- Réduire les temps de réponse d'un Flask SQLAlchemy site web
- Utilisation des Python's pyOpenSSL pour vérifier les certificats SSL téléchargés d'un hôte
- Connexion à un service sur un hôte Docker à partir d'un conteneur Docker
- Utiliser UUIDs au lieu de Integer Autoincrement Primary Keys avec SQLAlchemy et MariaDb
- SQLAlchemy : Utilisation de Cascade Deletes pour supprimer des objets connexes