Dans une application Web sur laquelle je travaille, toutes les opérations de base de données sont abstraites à l'aide de certains référentiels génériques définis sur Entity Framework ORM.
Cependant, afin d'avoir une conception simple pour les référentiels génériques, toutes les tables impliquées doivent définir un entier unique (Int32
En C #, int
en SQL). Jusqu'à présent, cela a toujours été le PK de la table et aussi le IDENTITY
.
Les clés étrangères sont fortement utilisées et font référence à ces colonnes entières. Ils sont nécessaires à la fois pour la cohérence et pour générer des propriétés de navigation par l'ORM.
La couche application effectue généralement les opérations suivantes:
SELECT * FROM table
UPDATE table SET Col1 = Val1 WHERE Id = IdVal
DELETE FROM table WHERE Id = IdVal
INSERT INTO table (cols) VALUES (...)
Opérations moins fréquentes:
BULK INSERT ... into table
Suivi (*) de toutes les données (pour récupérer les identifiants générés)DELETE FROM table where OtherThanIdCol = SomeValue
UPDATE table SET SomeCol = SomeVal WHERE OtherThanIdCol = OtherValue
* toutes les petites tables sont mises en cache au niveau de l'application et presque tous SELECTs
n'atteindront pas la base de données. Un modèle typique est la charge initiale et beaucoup de INSERT
, UPDATE
et DELETE
.
Selon l'utilisation actuelle de l'application, il y a très peu de chances d'atteindre 100 millions d'enregistrements dans l'une des tables.
Question: Du point de vue d'un DBA, y a-t-il des problèmes importants que je peux rencontrer en ayant cette limitation de conception de table?
[MODIFIER]
Après avoir lu les réponses (merci pour les bons commentaires) et les articles référencés, j'ai l'impression que je dois ajouter plus de détails:
Spécificités de l'application actuelle - Je n'ai pas mentionné l'application Web actuelle, car je veux savoir si le modèle peut également être réutilisé pour d'autres applications. Cependant, mon cas particulier est une application qui extrait de nombreuses métadonnées d'un DWH. Les données source sont assez désordonnées (dénormalisées de manière étrange, présentant des incohérences, aucun identifiant naturel dans de nombreux cas, etc.) et mon application génère des entités clairement séparées. De plus, de nombreux identifiants générés (IDENTITY
) sont affichés, afin que l'utilisateur puisse les utiliser comme clés professionnelles. Ceci, outre une refactorisation massive du code, exclut l'utilisation des GUID .
"ils ne devraient pas être le seul moyen d'identifier de manière unique une ligne" (Aaron Bertrand ♦) - c'est un très bon conseil. Tous mes tableaux définissent également une CONTRAINTE UNIQUE pour garantir que les doublons d'entreprise ne sont pas autorisés.
Conception pilotée par l'application frontale par rapport à la conception pilotée par la base de données - le choix de conception est dû à ces facteurs
Limitations d'Entity Framework - plusieurs colonnes PK sont autorisées, mais leurs valeurs ne peuvent pas être mises à jour
Limitations personnalisées - le fait d'avoir une seule clé entière simplifie considérablement les structures de données et le code non SQL. Par exemple: toutes les listes de valeurs ont une clé entière et des valeurs affichées. Plus important encore, il garantit que toute table marquée pour la mise en cache pourra être placée dans une carte Unique int key -> value
.
Requêtes de sélection complexes - cela ne se produira presque jamais car toutes les données des petites tables (<20-30K enregistrements) sont mises en cache au niveau de l'application. Cela rend la vie un peu plus difficile lors de l'écriture de code d'application (plus difficile à écrire LINQ), mais la base de données est beaucoup plus agréable:
Les vues de liste - ne généreront aucune requête SELECT
lors du chargement (tout est mis en cache) ou des requêtes qui ressemblent à ceci:
SELECT allcolumns FROM BigTable WHERE filter1 IN (val1, val2) AND filter2 IN (val11, val12)
Toutes les autres valeurs requises sont récupérées via les recherches de cache (O (1)), donc aucune requête complexe ne sera générée.
Modifier les vues - générera des instructions SELECT
comme ceci:
SELECT allcolumns FROM BigTable WHERE PKId = value1
(tous les filtres et valeurs sont int
s)
Outre l'espace disque supplémentaire (et à son tour l'utilisation de la mémoire et des E/S), il n'y a pas vraiment de dommage dans l'ajout d'une colonne IDENTITY même aux tables qui n'en ont pas besoin (un exemple de table qui n'a pas besoin d'une colonne IDENTITY est une simple table de jonction, comme le mappage d'un utilisateur à ses autorisations).
Je me moque de les ajouter aveuglément à chaque table dans un article de blog de 2010:
Mais les clés de substitution ont des cas d'utilisation valides - faites juste attention à ne pas supposer qu'elles garantissent l'unicité (ce qui est parfois la raison pour laquelle elles sont ajoutées - elles ne devraient pas être les seulement façon d'identifier de manière unique une ligne). Si vous devez utiliser un cadre ORM et que votre cadre ORM nécessite des clés entières à colonne unique même dans les cas où votre clé réelle n'est pas un entier, ou pas une seule colonne, ou aucune, assurez-vous de définir des contraintes/index uniques pour vos vraies clés aussi.
D'après mon expérience, la raison principale et écrasante d'utiliser un ID distinct pour chaque table est la suivante:
Dans presque tous les cas, mon client a prêté serment de sang dans la phase de conception qu'un champ externe, "naturel" XYZBLARGH_ID
restera unique pour toujours, et ne changera jamais pour une entité donnée, et ne sera jamais réutilisé, il est finalement apparu des cas où les propriétés de la clé primaire ont été brisées. Cela ne fonctionne tout simplement pas de cette façon.
Ensuite, d'un point de vue DBA, les choses qui ralentissent ou gonflent une base de données ne sont certainement pas 4 octets (ou autre) par ligne, mais des éléments comme des index incorrects ou manquants, des réorganisations de table/index oubliées, de mauvais paramètres de réglage RAM/tablespace , en négligeant d'utiliser des variables de liaison, etc. Ceux-ci peuvent ralentir la base de données par des facteurs de 10, 100, 10000 ... pas une colonne ID supplémentaire.
Donc, même s'il y avait un inconvénient technique et mesurable à avoir 32 bits supplémentaires par ligne, il ne s'agit pas de savoir si vous pouvez optimiser l'ID, mais si l'ID sera essentiel à un moment donné, ce qui sera plus probable qu'improbable. Et je ne vais pas compter tous les avantages "logiciels" d'une position de développement logiciel (comme votre exemple ORM, ou le fait que cela facilite la tâche des développeurs de logiciels lorsque tous les ID par conception ont le même type de données, etc.) .
N.B .: notez que vous n'avez pas besoin d'un ID distinct pour n:m
tables d'association car pour ces tables, les ID des entités associées doivent former une clé primaire. Un contre-exemple serait bizarre n:m
association qui permet plusieurs associations entre les deux mêmes entités pour une raison bizarre - celles-ci auraient alors besoin de leur propre colonne ID pour créer un PK. Il y a bibliothèques ORM qui ne peuvent pas gérer les PK multi-colonnes, donc ce serait une raison d'être indulgent avec les développeurs, s'ils doivent travailler avec une telle bibliothèque.
Si vous ajoutez invariablement une colonne supplémentaire vide de sens à chaque table et référencez uniquement ces colonnes en tant que clés étrangères, vous rendrez presque inévitablement la base de données plus complexe et difficile à utiliser. En effet, vous supprimerez les données présentant un intérêt pour les utilisateurs des attributs de clé étrangère et forcerez l'utilisateur/l'application à effectuer une jointure supplémentaire pour récupérer ces mêmes informations. Les requêtes deviennent plus complexes, le travail de l'optimiseur devient plus difficile et les performances peuvent en souffrir.
Vos tableaux seront plus peu peuplés de données "réelles" qu'ils ne l'auraient été autrement. La base de données sera donc plus difficile à comprendre et à vérifier. Vous pouvez également trouver difficile ou impossible d'appliquer certaines contraintes utiles (où les contraintes impliqueraient plusieurs attributs qui ne sont plus dans la même table).
Je vous suggère de choisir vos clés plus soigneusement et de les rendre entières uniquement si/quand vous avez de bonnes raisons de le faire. Basez vos conceptions de base de données sur une bonne analyse, l'intégrité des données, l'aspect pratique et des résultats vérifiables plutôt que de vous fier à des règles dogmatiques.
D'après mon expérience avec diverses bases de données, une clé primaire Integer est toujours meilleure que les applications qui ont aucune clé définie du tout. Ou qui ont des clés qui joignent une demi-douzaine de colonnes varchar de manière maladroite qui ne le sont pas logique ... (soupir)
J'ai vu des applications qui sont passées de PK entiers à des GUID. La raison pour laquelle ils l'ont fait était parce qu'il était nécessaire de fusionner les données de plusieurs bases de données sources dans certains cas. Les développeurs ont changé tous les clés des GUID pour que les fusions puissent se produire sans crainte de collisions de données, même sur des tables qui ne faisaient pas partie de la fusion (juste au cas où ces tables feraient jamais partie d'un fusion future).
Je dirais qu'un PK entier ne va pas vous mordre à moins que vous ne prévoyiez de fusionner des données provenant de sources distinctes ou que vous ayez des données qui dépassent vos limites de taille entières - tout cela est amusant et amusant jusqu'à ce que vous manquiez d'espace pour les insertions .
Je dirai cependant que cela peut est logique de définir votre index cluster sur une colonne autre que votre PK, si la table sera interrogée plus fréquemment de cette façon. Mais c'est un cas inhabituel, surtout si la majeure partie des mises à jour et des sélections sont basées sur les valeurs PK.
En mettant de côté:
À condition que vous utilisiez la suppression/mise à jour en masse, le cas échéant, et que vous disposiez d'index pour prendre en charge de telles opérations, je ne pense pas que vous rencontrerez des problèmes en raison de la norme PK que vous utilisez.
Il est possible que si vous demandez à EF de générer des requêtes avec des jointures, etc., elles ne soient pas aussi efficaces qu'elles le seraient avec un référentiel basé sur des clés naturelles, mais je ne connais pas suffisamment ce domaine pour dire à coup sûr de toute façon.
Vous avez quelques facteurs pour vous guider,
Définition et spécification
Si quelque chose est défini comme unique par la tâche ou les lois de la physique, vous perdez votre temps avec une clé de substitution.
Unicité.
Pour la raison personnelle, les jointures et les fonctionnalités de base de données de niveau supérieur, vous aurez besoin de: (a) colonne unique, (b) série unique de colonnes
Tous les schémas suffisamment normalisés (1NF) fournissent l'un des éléments suivants. S'ils ne le font pas, vous devriez toujours en créer un. Si vous avez une liste de personnes prévues pour faire du bénévolat dimanche, et cela comprend le nom et le prénom, vous voudrez savoir quand vous avez deux Joe Bobs.
Implémentation et optimisation.
Un int a tendance à être un petit formulaire de données qui est rapide pour la comparaison et l'égalité. Comparez cela avec une chaîne Unicode dont le classement peut dépendre des paramètres régionaux (emplacement et langue). Le stockage d'un 4242 dans une chaîne ASCII/UTF8 fait 4 octets. En le stockant sous forme d'entier, il tient dans 2 octets.
Donc, en ce qui concerne les inconvénients, vous avez quelques facteurs.
Confusion et ambiguïté.
Espace.
Les entiers ajoutent toujours de l'espace à la ligne. Et, si vous ne les utilisez pas, cela ne sert à rien.
Clustering.
Vous ne pouvez commander vos données que dans un sens. Si vous imposez une clé de substitution qui n'est pas nécessaire, effectuez-vous un cluster de cette façon ou de la manière de la clé naturelle?