web-dev-qa-db-fra.com

Utilisation d'un GUID comme clé primaire

J'utilise généralement des ID d'incrémentation automatique comme clés primaires dans les bases de données. J'essaie de découvrir les avantages de l'utilisation des GUID. J'ai lu cet article: https://betterexplained.com/articles/the-quick-guide-to-guids/

Je me rends compte que ces GUID sont utilisés pour identifier les objets au niveau de l'application. Sont-ils également stockés en tant que clé primaire au niveau de la base de données. Par exemple, disons que j'ai eu la classe suivante:

public class Person
{
public GUID ID;
public string Name;
..

//Person Methods follow
}

Disons que je voulais créer une nouvelle personne en mémoire, puis insérer la personne dans une base de données. Puis-je simplement faire ceci:

Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

Supposons que j'avais une base de données contenant des millions et des millions de lignes avec un GUID comme clé primaire. Est-ce toujours unique? Suis-je même en train de comprendre correctement les GUID?

J'ai lu cet article plus tôt: http://enterprisecraftsmanship.com/2014/11/15/cqs-with-database-generated-ids/ . Cela m'embrouille un peu car il semble recommander un juste milieu entre les GUID et les entiers comme clés primaires.

Modifier le 11/06/18

J'en suis venu à croire que les Guids sont plus adaptés que les ints à mes besoins. J'utilise CQRS plus ces jours-ci et les GUID s'intègrent mieux.

Je remarque que certains développeurs modélisent les GUID sous forme de chaînes dans le modèle de domaine, par exemple ici: https://github.com/dotnet-architecture/eShopOnContainers/blob/dev/src/Services/Ordering/Ordering.Domain/AggregatesModel/BuyerAggregate/Buyer.cs - dans ce cas: IdentityGuid est un GUID modélisé sous la forme d'une chaîne. Y a-t-il une autre raison que celle indiquée ici: tilisez un objet de valeur personnalisé ou un GUID comme identificateur d'entité dans une distribution système? . Est-il "normal" de modéliser le GUID comme une chaîne ou dois-je le modéliser comme un GUID dans le modèle et la base de données ?

32
w0051977

Les GUID sont par définition des "identifiants uniques au monde". Il existe un concept similaire mais légèrement différent dans Java appelé UUID "Universally Unique IDentifiers". Les noms sont interchangeables pour toute utilisation pratique.

Les GUID sont au cœur de la façon dont Microsoft envisageait le clustering de bases de données, et si vous devez incorporer des données provenant de sources parfois connectées, elles aident vraiment à prévenir les collisions de données.

Quelques faits Pro-GUID:

  • Les GUID empêchent les collisions clés
  • Les GUID aident à fusionner des données entre réseaux, machines, etc.
  • SQL Server prend en charge les GUIDS semi-séquentiels pour aider à minimiser la fragmentation d'index ( ref , certaines mises en garde)

Une certaine laideur avec les GUID

  • Ils sont gros, 16 octets chacun
  • Ils sont hors service, vous ne pouvez donc pas trier sur l'ID et espérer obtenir l'ordre d'insertion comme vous le pouvez sur les ID d'incrémentation automatique
  • Ils sont plus lourds à utiliser, en particulier sur les petits ensembles de données (comme les tables de recherche)
  • La nouvelle GUID est plus robuste sur SQL Server que dans la bibliothèque C # (vous pouvez avoir des GUIDS séquentiels à partir de SQL Server, en C # c'est aléatoire))

Les GUID augmenteront la taille de vos index, donc le coût d'espace disque pour l'indexation d'une colonne sera plus élevé. Les GUID aléatoires fragmenteront vos index.

Si vous savez que vous n'allez pas synchroniser les données de différents réseaux, les GUID peuvent entraîner plus de surcharge qu'ils n'en valent.

Si vous avez besoin d'ingérer des données de clients parfois connectés, ils peuvent être beaucoup plus robustes pour empêcher les collisions de clés que de s'appuyer sur la définition de plages de séquences pour ces clients.

42
Berin Loritsch

Est-ce que ce sera toujours unique?

Toujours? non, pas toujours; c'est une séquence finie de bits.

Supposons que j'avais une base de données contenant des millions et des millions de lignes avec un GUID comme clé primaire.

Des millions et des millions, vous êtes probablement en sécurité. Un million de millions, et le probabilité de collision devient significatif. Il y a cependant une bonne nouvelle: vous avez déjà épuisé l'espace disque au moment où cela se produit.

Puis-je simplement faire ça?

Vous pouvez; ce n'est pas une très bonne idée. Votre modèle de domaine ne devrait normalement pas générer de nombres aléatoires; ils doivent être des entrées pour votre modèle.

Au-delà de cela, lorsque vous traitez avec un réseau peu fiable, où vous pouvez obtenir des messages en double, un UUID généré de manière déterministe vous protégera contre la présence d'entités en double. Mais si vous attribuez un nouveau nombre aléatoire à chacun, vous avez plus de travail à faire pour identifier la duplication.

Voir la description de l'uuid basé sur le nom dans RFC 4122

Est-il "normal" de modéliser le GUID comme une chaîne ou dois-je le modéliser comme un GUID dans le modèle et la base de données?)

Je ne pense pas que cela compte beaucoup. Pour la plupart de votre modèle de domaine, il s'agit d'un identifiant; la seule question que vous lui demandez est de savoir si elle est ou non identique à un autre identifiant. Votre modèle de domaine ne regarde normalement pas la représentation en mémoire d'un identifiant.

Si GUID est disponible en tant que "type primitif" dans votre paramètre agnostique de domaine, je l'utiliserais; il permet au contexte de prise en charge de choisir les optimisations appropriées qui peuvent être disponibles.

Ce que vous devez reconnaître, cependant, c'est que la représentation de l'identifiant, à la fois en mémoire et en stockage, est une décision que vous prenez dans votre implémentation, et donc vous devez prendre des mesures pour vous assurer que l'empreinte de code couplée à celle-ci la décision est petite - voir Parnas 1972 .

28
VoiceOfUnreason

Le GUID ou UUID sera très probablement nique en raison de la façon dont ils sont générés et ils fournir un moyen sûr de garantir l'unicité sans avoir à communiquer avec une autorité centrale.

Avantages des GUID en tant que clé primaire:

  • Vous pouvez copier des données entre différents fragments d'un cluster et ne pas avoir à vous soucier des collisions PK.
  • Il vous permet de connaître votre clé primaire avant d'avoir inséré des enregistrements.
  • Simplifie la logique de transaction pour l'insertion d'enregistrements enfants.
  • Ne peut pas être facilement deviné.

Dans l'exemple que vous avez fourni:

Person p1 = new Person();
p1.ID = GUID.NewGUID();
PersonRepository.Insert(p1);

La spécification de GUID avant l'heure d'insertion peut enregistrer un aller-retour dans la base de données lors de l'insertion d'enregistrements enfants successifs et vous permettre de les valider dans la même transaction.

Person p2 = new Person();
p2.ParentID = p1.ID
PersonRepository.Insert(p2);

Les inconvénients des GUID en tant que clé primaire:

  • Ils sont grands de 16 octets, ce qui signifie qu'ils consommeront plus d'espace à mesure que les index et les clés étrangères sont ajoutés.
  • Ils ne trient pas bien car ce sont essentiellement des nombres aléatoires.
  • L'utilisation de l'index est très, très, très mauvaise.
  • Beaucoup de feuilles qui bougent.
  • Ils sont difficiles à retenir.
  • Ils sont difficiles à verbaliser.
  • Ils peuvent rendre les URL plus difficiles à lire.

Si votre application n'a pas besoin de partitionnement ou de clustering, il serait préférable de s'en tenir à des types de données plus petits et plus simples tels que int ou bigint.

De nombreuses bases de données ont leurs propres implémentations internes qui tentent d'atténuer les problèmes de stockage causés par les GUID et SQL Server a même une fonction newsequentialid pour aider à la commande des UUID permettant une meilleure utilisation des index et ils ont généralement de meilleures performances les caractéristiques.

De plus, du point de vue d'un testeur, d'un utilisateur ou d'un développeur travaillant avec l'application, l'utilisation d'un ID sur un GUID améliorera considérablement la communication. Imaginez devoir lire un GUID sur un téléphone.

En fin de compte, à moins que le clustering à grande échelle ou l'URL d'obscurcissement ne soit une exigence, il est plus pragmatique de s'en tenir aux ID à incrémentation automatique.

11
icirellik
Person p1 = new Person();
p1.ID=GUID.NewGUID();
PersonRepository.Insert(p1);

C'est de loin la raison la plus importante pour l'utilisation des GUID.

Le fait que vous puissiez créer un identifiant unique sans que votre code connaisse ou communique avec votre couche de persistance est un énorme avantage.

Vous pouvez être sûr que l'objet Personne que vous venez de générer sur votre serveur, téléphone pc, ordinateur portable, appareil hors ligne ou tout ce qui est unique sur tous vos serveurs partout dans le monde, quelle que soit sa distribution.

Vous pouvez le coller dans n'importe quel type de base de données rdb ou no-sql, fichier, l'envoyer à n'importe quel service Web ou le jeter immédiatement comme inédit

Non, vous n'obtiendrez jamais de collision.

Oui, les inserts peuvent être légèrement plus lents car il peut être nécessaire de manipuler l'index.

Oui, c'est plus grand qu'un int.

  • éditer. a dû tirer avant de terminer.

Je sais que beaucoup de gens sont très attachés aux pouces auto inc et c'est un sujet controversé avec les DBA

Mais je ne peux vraiment pas dire assez clairement à quel point les guides sont supérieurs. Vous devez utiliser les guides par par défaut dans n'importe quelle application.

les incs automobiles ont de nombreux défauts

  • Vous utilisez une base de données distribuée sans SQL. Vous ne pouvez tout simplement pas parler à toutes les autres instances pour savoir quel est le prochain numéro.

  • Vous utilisez un système de file d'attente de messages. Les choses ont besoin d'identifiants avant de toucher la base de données

  • Vous créez plusieurs éléments et les modifiez avant d'enregistrer. Chacun a besoin d'un identifiant avant d'avoir touché la base de données

  • Vous souhaitez supprimer et réinsérer des lignes. Assurez-vous de ne pas compter vos identifiants automatiques et de manquer!

  • Vous ne voulez pas exposer le nombre de commandes que vous avez prises cette année à chaque utilisateur

  • Vous souhaitez déplacer les données anonymisées de la production vers les tests et conserver les relations intactes. Mais ne supprimez pas toutes les données de test existantes.

  • Vous souhaitez fusionner votre produit à locataire unique dans une base de données à locataires multiples, mais tout le monde a une commande 56.

  • Vous créez des objets persistants mais éphémères. (commandes incomplètes) encore une fois, n'utilisez pas toutes vos ints avec des trucs qui n'existent plus.

La liste est interminable et ce sont tous de vrais problèmes qui arrivent aux gens tout le temps. contrairement à manquer d'espace disque en raison de cols FK légèrement plus grands

Enfin, le problème majeur avec les ints est vous en manquez !!! ok en théorie vous ne le faites pas, il y a des charges. Mais en pratique, vous le faites parce que les gens ne les traitent pas comme des nombres aléatoires sans signification. ils font des choses comme

  • oh je ne veux pas que les clients pensent que nous sommes nouveaux. commencer à 10000

  • J'ai dû importer une charge de données donc j'ai juste augmenté la graine à 1 m pour que nous sachions ce qui est importé

  • nous avons besoin de catégories de données. chaque période commence au prochain million afin que nous puissions utiliser les premiers chiffres comme un nombre magique

  • J'ai supprimé et réimporté à nouveau toutes les données avec de nouveaux identifiants. Oui, même les journaux d'audit.

  • utiliser ce numéro, qui est une clé composite, comme id de cette autre chose

4
Ewan

Je dirais non, n'utilisez pas les GUID comme clés primaires. Je suis en fait confronté à une telle base de données maintenant, et ils sont l'une des principales causes de problèmes de performances.

Les 12 octets supplémentaires s'additionnent rapidement; rappelez-vous, la plupart des PK seront des FK dans d'autres tables, et seulement trois FK dans une table vous avez maintenant 48 octets supplémentaires pour chaque ligne. Cela s'additionne dans la table et dans les index. Il s'additionne également dans les E/S disque. Ces 12 octets supplémentaires doivent être lus et écrits.

Et si vous n'utilisez pas de GUID séquentiels et que les PK sont regroupés (ce qui se produit par défaut), SQL devra de temps en temps déplacer des pages entières de données pour les presser davantage au bon endroit. Pour une base de données hautement transactionnelle avec beaucoup d'insertions, de mises à jour et de suppressions, les choses s'enlisent rapidement.

Si vous avez besoin d'une sorte d'identifiant unique pour la synchronisation ou quelque chose, ajoutez une colonne guid. Il suffit de ne pas en faire le PK.

4
Andy

tilisez toujours les clés primaires (PK) générées par base de données et auto-incrémentées.

Pourquoi utiliser l'incrémentation automatique au lieu de GUID/UUID?

  • Les GUID (UUID) n'empêchent pas les collisions de clés car elles ne sont pas uniques et il n'y a aucun moyen de les rendre uniques car elles sont générées à partir de nombreuses sources.
  • Les GUID ne facilitent pas la fusion, car ils augmentent considérablement le processus de fusion, déjà long, avec des colonnes PK et FK extrêmement longues et non entières qui prennent beaucoup de temps à traiter. N'oubliez pas que pour la plupart des PK, il y aura au moins 1 autre table avec au moins 2 clés de la même taille: c'est son propre PK et un FK de retour à la première table. Tous doivent être résolus dans une fusion.

Mais comment alors gérer les éclats, les clusters, etc.?

  • Créez des PK à plusieurs colonnes constitués de colonnes distinctes identifiant chaque fragment/cluster/base de données/tout ce qui gère ses propres clés d'incrémentation automatique. Par exemple...

Un PK à 3 colonnes pour une table en cluster peut être ...

 DB | SH | KEY     |
----|----|---------|
 01 | 01 | 1234567 |

Mais qu'en est-il ...?

  • Voyages multiples dans la base de données - La plupart des applications n'ont pas besoin d'identifier de façon unique un enregistrement en cours de création jusqu'à ce qu'il soit inséré dans la base de données, car ce thread/session/tout ce qui ne fonctionne que sur un à la fois. Si l'application a vraiment besoin de cette capacité, utilisez une PK temporaire générée par l'application qui n'est pas envoyée à la base de données . Laissez la base de données mettre ensuite son propre PK à incrémentation automatique sur la ligne lorsqu'elle est insérée. Les insertions utiliseront le PK temporaire, tandis que les mises à jour et les suppressions utiliseront le PK permanent attribué par la base de données.

  • Performances - Les ordinateurs peuvent traiter des entiers simples beaucoup plus rapidement qu'autre chose en raison du domaine beaucoup plus grand si possible des valeurs par élément dans un GUID (37) par rapport à un entier (10). N'oubliez pas non plus que chaque caractère dans un GUID doit d'abord être converti en nombre pour être manipulé par le CPU.

Mauvais usages courants des clés primaires Les PK n'ont qu'un seul but ... identifier de manière absolument unique une ligne dans une table. Tout le reste est une mauvaise utilisation trop courante.

Détection des enregistrements manquants

  • Les enregistrements manquants ne peuvent pas être détectés en regardant les PK. Bénissez QA pour au moins essayer d'assurer la qualité des données. Cependant, eux et le manque de compréhension du programmeur sur la façon dont les clés des systèmes de base de données modernes sont attribués les conduisent souvent à croire qu'un numéro manquant dans un PK à incrémentation automatique signifie des données manquantes. Il ne pas parce que ...
  • Pour des performances optimales, les systèmes de base de données allouent des blocs de nombres en "séquences" (lots, plages) pour minimiser les déplacements vers la base de données réelle en stockage. La taille de ces séquences de nombres est souvent sous le contrôle du DBA mais peut ne pas être ajustable sur une base par table.
  • La clé à retenir est que ... les numéros inutilisés de ces séquences ne sont jamais renvoyés dans la base de données, il y a donc toujours des lacunes dans les numéros PK.
  • Pourquoi y aurait-il des numéros inutilisés que vous demandez? Parce qu'une variété d'actions de maintenance de la base de données peut entraîner l'abandon de séquences. Ce sont des choses comme les redémarrages, les rechargements en masse de tables, certains types de restauration à partir de sauvegardes et d'autres opérations.

Tri

  • Le tri par PK est très sujet aux erreurs car la plupart des gens penseront qu'il répertorie les lignes dans l'ordre où elles ont été créées et que cela correspond à l'heure de l'horloge. Surtout, mais pas nécessairement.
  • Les moteurs de base de données sont optimisés pour des performances maximales et cela peut signifier de retarder l'insertion des résultats d'une transaction compliquée de longue durée afin d'insérer des transactions simples et courtes, "out-of-turn" pour ainsi dire.
3
DocSalvager

Je me rends compte que ces GUID sont utilisés pour identifier les objets au niveau de l'application. Sont-ils également stockés en tant que clé primaire au niveau de la base de données.

C'est là que vous devez vous arrêter, juste là, et repenser.

La clé primaire de votre base de données ne doit JAMAIS avoir de sens commercial. Il devrait être dénué de sens par définition.

Ajoutez donc le GUID comme clé de votre entreprise et une clé primaire normale (généralement un entier long) comme clé primaire de la base de données. Vous pouvez toujours mettre un index unique sur le GUID pour garantir l'unicité.

Cela parle bien sûr de la théorie des bases de données, mais c'est également une bonne pratique. J'ai traité des bases de données où les clés primaires avaient une signification commerciale (un client avait pensé à économiser certaines ressources de base de données en les utilisant comme numéros d'employé, numéros de client, etc. etc. par exemple) et cela causait toujours des problèmes.

2
jwenting

Comme tout, il y a des avantages et des inconvénients à le faire:

Le bon:

  1. Vos clés ont toujours la même longueur (les très grandes bases de données peuvent avoir de très grandes clés)

  2. L'unicité est à peu près garantie - même lorsque vous les générez à partir d'un système distinct et/ou que vous n'avez pas lu le dernier ID de la base de données

Le mauvais:

  1. Comme mentionné ci-dessus - des index et un magasin de données plus importants.

  2. Vous ne pouvez pas commander par ID, vous devez commander par autre chose. Plus d'index, probablement moins efficaces.

  3. Ils sont moins lisibles par l'homme. Les entiers sont généralement plus faciles à analyser, à mémoriser et à taper pour les gens. L'utilisation de GUID comme ID dans les clauses WHERE sur plusieurs tables jointes peut faire fondre votre tête.

Comme tout, utilisez-les le cas échéant, ne soyez pas dogmatique - dans de nombreuses situations, les entiers à incrémentation automatique sont meilleurs, parfois les GUID sont excellents.

2
Phil S

Oui, vous pouvez utiliser GUID comme clé primaire. L'inconvénient est la taille et la fragmentation rapide de l'index.

Sauf si vous avez besoin d'unicité entre les bases de données (par exemple, un cluster), l'entier est préféré.

0
paparazzo

Voici mon point de vue sur ce problème - la solution est à mi-chemin entre GUID et les valeurs int, en prenant le meilleur des deux.

La classe génère une valeur Id pseudo aléatoire (mais augmentant avec le temps), qui est similaire à un Comb GUID .

Le principal avantage est qu'il permet de générer des valeurs Id sur le client, plutôt que d'utiliser des valeurs d'auto-incrémentation générées sur le serveur (ce qui nécessite un aller-retour) avec un risque presque nul de valeurs dupliquées.

Les valeurs générées utilisent uniquement 8 octets au lieu de 16 pour un GUID et ne dépendent pas d'un ordre de tri de base de données spécifique (par exemple Sql Server for GUIDs ). Les valeurs pourraient être développées pour utiliser toute la longue plage non signée, mais cela entraînerait des problèmes avec toute base de données ou autre référentiel de données qui ne possède que des types entiers signés.

public static class LongIdGenerator
{
    // set the start date to an appropriate value for your implementation 
    // DO NOT change this once any application that uses this functionality is live, otherwise existing Id values will lose their implied date
    private static readonly DateTime PeriodStartDate = new DateTime(2017, 1, 1, 0, 0, 0, DateTimeKind.Utc);
    private static readonly DateTime PeriodEndDate = PeriodStartDate.AddYears(100);
    private static readonly long PeriodStartTicks = PeriodStartDate.Ticks;
    private static readonly long PeriodEndTicks = PeriodEndDate.Ticks;
    private static readonly long TotalPeriodTicks = PeriodEndTicks - PeriodStartTicks;

    // ensures that generated Ids are always positve
    private const long SEQUENCE_PART_PERMUTATIONS = 0x7FFFFFFFFFFF; 

    private static readonly Random Random = new Random();

    private static readonly object Lock = new object();
    private static long _lastSequencePart;

    public static long GetNewId()
    {
        var sequencePart = GetSequenceValueForDateTime(DateTime.UtcNow);

        // extra check, just in case we manage to call GetNewId() twice before enough ticks have passed to increment the sequence 
        lock (Lock)
        {
            if (sequencePart <= _lastSequencePart)
                sequencePart = _lastSequencePart + 1;

            _lastSequencePart = sequencePart;
        }

        // shift so that the sequence part fills the most significant 6 bytes of the result value
        sequencePart = (sequencePart << 16);

        // randomize the lowest 2 bytes of the result, just in case two different client PCs call GetNewId() at exactly the same time
        var randomPart = Random.Next() & 0xFFFF;

        return sequencePart + randomPart;
    }

    // used if you want to generate an Id value for a historic time point (within the start and end dates)
    // there are no checks, compared to calls to GetNewId(), but the chances of colliding values are still almost zero
    public static long GetIdForDateTime(DateTime dt)
    {
        if (dt < PeriodStartDate || dt > PeriodStartDate)
            throw new ArgumentException($"value must be in the range {PeriodStartDate:dd MMM yyyy} - {PeriodEndDate:dd MMM yyyy}");

        var sequencePart = GetSequenceValueForDateTime(dt.ToUniversalTime());
        var randomPart = Random.Next() & 0xFFFF;
        return ( sequencePart << 16 ) + randomPart;
    }

    // Get a 6 byte sequence value from the specified date time - startDate => 0 --> endDate => 0x7FFFFFFFFFFF
    // For a 100 year time period, 1 unit of the sequence corresponds to about 0.022 ms
    private static long GetSequenceValueForDateTime(DateTime dt)
    {
        var ticksFromStart = dt.ToUniversalTime().Ticks - PeriodStartTicks;
        var proportionOfPeriod = (decimal)ticksFromStart / TotalPeriodTicks;
        var result = proportionOfPeriod * SEQUENCE_PART_PERMUTATIONS;
        return (long)result;
    }

    public static DateTime GetDateTimeForId(long value)
    {
        // strip off the random part - the two lowest bytes
        var timePart = value >> 16;
        var proportionOfTotalPeriod = (decimal) timePart / SEQUENCE_PART_PERMUTATIONS;
        var ticks = (long)(proportionOfTotalPeriod * TotalPeriodTicks);
        var result = PeriodStartDate.AddTicks(ticks);
        return result;
    }
}
0
Peregrine