web-dev-qa-db-fra.com

Pourquoi le modèle relationnel d'une base de données est-il important?

J'approche d'un projet où je vais devoir mettre en place une base de données avec mon patron; nous sommes une toute petite start-up donc l'environnement de travail est profondément personnel.

Il m'avait donné une des bases de données de l'entreprise auparavant et cela allait complètement à l'encontre de ce que j'avais appris (et lu) à l'école pour RDBMS. Par exemple, il existe ici des bases de données entières qui se composent d'une seule table (par base de données indépendante). L'une de ces tables comprend plus de 20 colonnes et pour le contexte, voici quelques noms de colonnes de la table one:

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

Le fait est que là où il devrait avoir des tables individuelles qui contiennent les données d'entité (nom, taille, date d'achat, etc.), il les place toutes dans une grande table par base de données.

Je veux améliorer cette conception, mais je ne sais pas pourquoi un modèle de données correctement normalisé et segmenté améliorerait réellement ce produit. Bien que je sois familier avec la conception de bases de données au collège et que je comprenne comment pour le faire, je ne suis pas sûr pourquoi cela améliore réellement les bases de données.

Pourquoi un bon schéma relationnel améliore-t-il une base de données?

61
8protons

L'argument de performance est généralement celui qui est le plus intuitif. Vous voulez surtout souligner combien il sera difficile d'ajouter de bons index dans une base de données mal normalisée (remarque: il y a des cas Edge où la dénormalisation peut en fait améliorer les performances, mais quand vous êtes tous les deux inexpérimentés avec bases de données relationnelles, vous ne verrez probablement pas facilement ces cas).

Un autre est l'argument de la taille du stockage. Une table dénormalisée avec beaucoup de redondances nécessitera beaucoup plus de stockage. Cela joue également dans l'aspect performances: plus vous disposez de données, plus vos requêtes seront lentes.

Il y a aussi un argument qui est un peu plus difficile à comprendre, mais qui est en fait plus important parce que vous ne pouvez pas le résoudre en y jetant plus de matériel. C'est le problème de cohérence des données. Une base de données correctement normalisée veillera par elle-même à ce qu'un produit avec un ID spécifique porte toujours le même nom. Mais dans une base de données dénormalisée, de telles incohérences sont possibles, donc une attention particulière doit être prise lorsqu'il s'agit d'éviter les incohérences, ce qui prendra du temps de programmation pour bien fonctionner et causera toujours des bogues qui vous coûteront pour la satisfaction du client.

70
Philipp

Je vais devoir mettre en place une base de données avec mon patron ...

Utiliser la gestion de base de données dédiée logiciel pourrait être considérablement plus facile (désolé; je n'ai pas pu résister).

lngStoreID | vrStoreName | lngCompanyID | vrCompanyName | lngProductID | vrProductName

Si cette base de données ne se soucie que de "journaliser" quel produit a été vendu où, quand et par qui, alors vous pourriez être capable d'étirer la définition de "base de données OK" suffisamment loin pour la couvrir. Si ces données sont utilisées pour n'importe quoi autrement, alors elles sont vraiment assez pauvres.

Mais ...

L'application/les requêtes utilisant ces données répondent-elles mal/lentement? Sinon, il n'y a pas de vrai problème à résoudre. Bien sûr, cela a l'air et se sent laid, mais si cela fonctionne alors vous n'obtiendrez pas de "points" pour suggérer que cela "pourrait" être mieux.

Si vous pouvez trouver des symptômes précis (c'est-à-dire des problèmes) qui semblent être causés par une mauvaise modélisation des données, alors créez une meilleure solution. Prenez une copie de l'une de ces "bases de données", normalisez les données et voyez si votre solution fonctionne mieux. Si c'est considérablement mieux (et je m'attendrais à ce que tout les opérations de mise à jour sur ces données soient massivement améliorées), revenez à votre patron et leur montrer l'amélioration.

Il est parfaitement possible de recréer sa "vue table unique" des données avec .. enfin .. Vues.

24
Phill W.

Pourquoi un bon schéma relationnel améliore-t-il une base de données?

La réponse est: cela n'améliore pas toujours une base de données. Vous devez savoir que ce que vous avez probablement appris s'appelle troisième forme normale .

D'autres formulaires sont valables dans certaines situations, ce qui est essentiel pour répondre à votre question. Votre exemple ressemble à First Normal Form , si cela vous aide à vous sentir mieux dans son état actuel.

Les règles 3NF établissent des relations entre les données qui "améliorent" une base de données:

  1. Empêchez les données non valides d'entrer dans votre système (si une relation est 1 contre 1, elle force une erreur malgré le code écrit dessus). Si vos données sont cohérentes dans la base de données, il est moins susceptible d'entraîner des incohérences en dehors de votre base de données.

  2. Il fournit un moyen de valider le code (par exemple, une relation plusieurs-à-un est un signal pour restreindre les propriétés/comportements d'un objet). Lors de l'écriture de code pour utiliser la base de données, les programmeurs remarquent parfois la structure des données comme un indicateur du fonctionnement de leur code. Ou ils peuvent fournir des commentaires utiles si la base de données ne correspond pas à leur code. (Cela ressemble plus à un vœu pieux, malheureusement.)

  3. Fournissez des règles qui peuvent vous aider à réduire considérablement les erreurs lors de la création d'une base de données, de sorte que vous ne la construisez pas en fonction d'exigences arbitraires pouvant survenir à tout moment au cours de la vie d'une base de données. Au lieu de cela, vous évaluez systématiquement les informations pour atteindre des objectifs spécifiques.

  4. Des structures de base de données appropriées améliorent les performances en connectant les données de manière à minimiser le stockage des données, à minimiser les appels de stockage pour récupérer les données, à maximiser les ressources en mémoire et/ou à minimiser le tri/la manipulation des données pour l'ensemble de données que vous avez, par rapport à la requête que vous êtes exécuter contre elle. Mais la structure "appropriée" dépend de la quantité de données, de la nature des données, du type de requête, des ressources système, etc. En normalisant, vous pouvez aggraver les performances (c.-à-d. Si vous chargez toutes les données en une seule table - la jointure peut ralentir une requête). Le traitement des transactions (OLTP) et l'intelligence d'affaires (entrepôt de données) sont très différents.

Dans une petite entreprise avec de petits ensembles de données, vous constaterez peut-être qu'il n'y a rien de mal avec la façon dont c'est maintenant. Sauf que si vous grandissez, il sera difficile de "corriger" plus tard, car à mesure que la table grossit, les systèmes qui l'utilisent vont probablement ralentir.

Habituellement, vous souhaiterez mettre l'accent sur les transactions rapides à mesure que l'entreprise grandit. Cependant, si vous passez du temps sur ce projet maintenant au lieu d'autres choses dont l'entreprise peut avoir besoin de manière plus urgente, vous n'aurez peut-être jamais ce problème car votre entreprise ne se développe jamais vraiment. C'est le "défi de pré-optimisation" - où passer votre temps précieux dès maintenant.

Bonne chance!

14
Jim

Il y a plusieurs raisons pour lesquelles l'utilisation d'une grande "table divine" est mauvaise. Je vais essayer d'illustrer les problèmes avec un exemple de base de données composée. Supposons que vous essayez de modéliser des événements sportifs. Nous dirons que vous voulez modéliser des jeux et les équipes qui jouent dans ces jeux. Une conception avec plusieurs tables pourrait ressembler à ceci (c'est très simpliste exprès donc ne vous laissez pas prendre dans des endroits où plus de normalisation pourrait être appliquée):

Teams
Id | Name | HomeCity

Games
Id | StartsAt | HomeTeamId | AwayTeamId | Location

et une base de données de table unique ressemblerait à ceci

TeamsAndGames
Id | TeamName | TeamHomeCity | GameStartsAt | GameHomeTeamId | GameAwayTeamId | Location

Voyons d'abord comment créer des index sur ces tables. Si j'avais besoin d'un index sur la ville d'origine pour une équipe, je pourrais l'ajouter à la table Teams ou à la table TeamsAndGames assez facilement. N'oubliez pas que chaque fois que vous créez un index, celui-ci doit être stocké quelque part sur le disque et mis à jour lorsque des lignes sont ajoutées à la table. Dans le cas de la table Teams, c'est assez simple. J'ai mis une nouvelle équipe, la base de données met à jour l'index. Mais qu'en est-il de TeamsAndGames? Eh bien, la même chose s'applique à l'exemple Teams. J'ajoute une équipe, l'index est mis à jour. Mais cela arrive aussi lorsque j'ajoute un jeu! Même si ce champ sera nul pour un jeu, l'index doit quand même être mis à jour et stocké sur le disque pour ce jeu. Pour un index, cela ne sonne pas trop mal. Mais lorsque vous avez besoin de nombreux index pour les multiples entités entassées dans ce tableau, vous perdez beaucoup d'espace de stockage des index et beaucoup de temps processeur pour les mettre à jour pour des choses où ils ne s'appliquent pas.

Deuxièmement, la cohérence des données. Dans le cas de l'utilisation de deux tables distinctes, je peux utiliser des clés étrangères de la table Games vers la table Teams pour définir les équipes qui jouent dans un jeu. Et en supposant que je ne puisse pas annuler les colonnes HomeTeamId et AwayTeamId, la base de données s'assurera que chaque jeu auquel je participe comporte 2 équipes et que ces équipes existent dans ma base de données. Mais qu'en est-il du scénario à table unique? Eh bien, comme il y a plusieurs entités dans ce tableau, ces colonnes doivent être nullables (vous pouvez les rendre non nullables et y insérer des données inutiles, mais ce n'est qu'une horrible idée). Si ces colonnes peuvent être annulées, la base de données ne peut plus garantir que lorsque vous insérez un jeu, elle a deux équipes.

Mais que se passe-t-il si vous décidez de vous lancer de toute façon? Vous configurez les clés étrangères de telle sorte que ces champs pointent vers une autre entité dans la même table. Mais maintenant, la base de données s'assurera simplement que ces entités existent dans la table, pas qu'elles sont du bon type. Vous pouvez très facilement définir GameHomeTeamId sur l'ID d'un autre jeu et la base de données ne se plaindra pas du tout. Si vous essayiez cela dans le scénario à plusieurs tables, la base de données ferait un ajustement.

Vous pouvez essayer d'atténuer ces problèmes en disant "eh bien, nous nous assurerons simplement de ne jamais le faire dans le code". Si vous êtes confiant dans votre capacité à écrire du code sans bogue la première fois et dans votre capacité à prendre en compte toutes les combinaisons étranges de choses qu'un utilisateur peut essayer, allez-y. Personnellement, je ne suis pas confiant dans ma capacité à faire l'une de ces choses, donc je vais laisser la base de données me donner un filet de sécurité supplémentaire.

(Cela devient encore pire si votre conception est celle où vous copiez toutes les données pertinentes entre les lignes au lieu d'utiliser des clés étrangères. Toute incohérence d'orthographe/autres données sera difficile à résoudre. Comment savoir si "Jon" est une faute d'orthographe de "John "ou si c'était intentionnel (parce que ce sont deux personnes distinctes)?)

Troisièmement, presque toutes les colonnes doivent pouvoir être annulées ou doivent être remplies de données copiées ou de déchets. Un jeu n'a pas besoin d'un TeamName ou TeamHomeCity. Donc, soit chaque jeu a besoin d'une sorte d'espace réservé, soit il doit être annulable. Et s'il est nullable, la base de données prendra volontiers un jeu sans TeamName. Il faudra également une équipe sans nom, même si votre logique métier dit que cela ne devrait jamais se produire.

Il y a une poignée d'autres raisons pour lesquelles vous voudriez des tables séparées (y compris la préservation de l'intégrité du développeur). Il y a même quelques raisons pour lesquelles une table plus grande pourrait être meilleure (la dénormalisation améliore parfois les performances). Ces scénarios sont rares (et généralement mieux gérés lorsque vous avez des mesures de performances pour montrer que c'est vraiment le problème, pas un index manquant ou autre chose).

Enfin, développez quelque chose qui sera facile à entretenir. Ce n'est pas parce que ça "fonctionne" que ça va. Essayer de maintenir des tables divines (comme les classes divines) est un cauchemar. Vous vous préparez à souffrir plus tard.

11
Becuzz

Citation du jour: " La théorie et la pratique devraient être les mêmes ... en théorie "

Tableau dénormalisé

Votre table hold-it-all unique contient des données redondantes a un avantage: elle rend les rapports sur ses lignes très simples à coder et rapides à exécuter car vous n'avez pas à faire de jointures. Mais cela à un coût élevé:

  • Il contient des copies redondantes des relations (par exemple IngCompanyID et vrCompanyName). La mise à jour des données de base peut nécessiter de mettre à jour beaucoup plus de lignes que dans un schéma normalisé.
  • Ça mélange tout. Vous ne pouvez pas garantir un contrôle d'accès facile au niveau de la base de données, par exemple s'assurer que l'utilisateur A ne peut mettre à jour que les informations de l'entreprise et l'utilisateur B uniquement les informations sur le produit.
  • Vous ne pouvez pas garantir des règles de cohérence au niveau de la base de données (par exemple, la clé primaire pour garantir qu'il n'y a qu'un seul nom de société pour un identifiant de société).
  • Vous ne bénéficiez pas pleinement de l'optimiseur de base de données qui pourrait identifier les stratégies d'accès optimales pour une requête complexe, en tirant parti de la taille des tables normalisées et des statistiques de plusieurs index. Cela pourrait rapidement compenser l'avantage limité d'éviter les jointures.

tableau normalisé

Les inconvénients ci-dessus sont des avantages pour le schéma normalisé. Bien sûr, les requêtes peuvent être un peu plus complexes à écrire.

En bref, le schéma normalisé exprime beaucoup mieux la structure et les relations entre vos données. Je serai provocateur et je dirai que c'est le même genre de différence qu'entre la discipline requise pour utiliser un ensemble de tiroirs de bureau ordonnés et la facilité d'utilisation d'une poubelle.

6
Christophe

Je pense qu'il y a au moins deux parties à votre question:

1. Pourquoi les entités de types différents ne devraient-elles pas être stockées dans la même table?

Les réponses les plus importantes ici sont la lisibilité et la vitesse du code. UNE SELECT name FROM companies WHERE id = ? est tellement plus lisible qu'un SELECT companyName FROM masterTable WHERE companyId = ? et vous risquez moins d'interroger accidentellement un non-sens (par exemple, SELECT companyName FROM masterTable WHERE employeeId = ? ne serait pas possible lorsque les entreprises et les employés sont stockés dans des tables différentes). Quant à la vitesse, les données d'une table de base de données sont récupérées soit en lisant la table complète séquentiellement, soit en lisant à partir d'un index. Les deux sont plus rapides si la table/l'index contient moins de données, et c'est le cas si les données sont stockées dans des tables différentes (et que vous avez seulement besoin de lire l'une des tables/index).

2. Pourquoi les entités d'un même type devraient-elles être divisées en sous-entités qui sont stockées dans des tables différentes?

Ici, la raison est principalement d'éviter les incohérences de données. Avec l'approche à table unique, pour un système de gestion des commandes, vous pouvez stocker le nom du client, l'adresse du client et l'ID de produit du produit que le client a commandé en tant qu'entité unique. Si un client commandait plusieurs produits, vous auriez plusieurs instances du nom et de l'adresse du client dans votre base de données. Dans le meilleur des cas, vous venez d'obtenir des données en double dans votre base de données, ce qui peut ralentir un peu. Mais le pire est que quelqu'un (ou un code) a fait une erreur lors de la saisie des données afin qu'une entreprise se retrouve avec des adresses différentes dans votre base de données. Cela seul est déjà assez mauvais. Mais si vous demandiez l'adresse d'une entreprise en fonction de son nom (par exemple SELECT companyAddress FROM orders WHERE companyName = ? LIMIT 1) vous obtiendrez simplement arbitrairement l'une des deux adresses retournées et ne réaliserez même pas qu'il y a une incohérence. Mais chaque fois que vous exécutez la requête, vous pouvez en fait obtenir une adresse différente, selon la façon dont votre requête est résolue en interne par le SGBD. Cela cassera probablement votre application ailleurs et la cause première de cette rupture sera très difficile à trouver.

Avec l'approche multi-tables, vous vous rendriez compte qu'il existe une dépendance fonctionnelle du nom de l'entreprise à l'adresse de l'entreprise (si une entreprise ne peut avoir qu'une seule adresse ), vous stockeriez le tuple (companyName, companyAddress) dans une table (par exemple company) et le tuple (productId, companyName) dans une autre table (par exemple order). Une contrainte UNIQUE sur la table company pourrait alors imposer que chaque entreprise ne possède qu'une seule adresse dans votre base de données afin qu'aucune incohérence pour les adresses de l'entreprise ne se produise.

Remarque: dans la pratique, pour des raisons de performances, vous générez probablement un ID de société unique pour chaque entreprise et l'utilisez comme clé étrangère au lieu d'utiliser directement le nom de l'entreprise. Mais l'approche générale reste la même.

5
Dreamer

TL; DR - Ils conçoivent la base de données en fonction de la façon dont ils ont été enseignés lorsqu'ils étaient en école.

J'aurais pu écrire cette question il y a 10 ans. Il m'a fallu un certain temps pour comprendre pourquoi mes prédécesseurs avaient conçu leurs bases de données comme ils le faisaient. Vous travaillez avec quelqu'un qui:

  1. A acquis la plupart de ses compétences en conception de bases de données en utilisant Excel comme base de données ou
  2. Ils utilisent les meilleures pratiques depuis leur sortie de l'école.

Je ne pense pas que ce soit le n ° 1 car vous avez en fait des numéros d'identification dans votre table, donc je suppose que le n ° 2.

Après avoir quitté l'école, je travaillais pour un magasin qui utilisait un AS/4 (alias IBM i). J'ai trouvé des choses étranges dans la façon dont ils ont conçu leurs bases de données et j'ai commencé à préconiser que nous apportions des changements pour suivre la façon dont on m'a appris à concevoir des bases de données. (j'étais stupide à l'époque)

Il a fallu un patient programmeur plus âgé pour m'expliquer pourquoi les choses se faisaient de cette façon. Ils n'avaient pas changé le schéma car cela aurait provoqué la rupture de programmes plus anciens que moi. Littéralement, le code source d'un programme avait une date de création de l'année avant ma naissance. Sur le système sur lequel nous travaillions, leurs programmes devait implémenter toute la logique et les opérations que le planificateur de requêtes de votre base de données gère pour vous. (Vous pouvez le voir en exécutant EXPLAIN sur l'une de vos requêtes)

Il était à jour sur les techniques que j'essayais d'implémenter, mais garder le système en marche était plus important que de faire des changements "parce que cela allait à l'encontre de ce qu'on m'avait appris". Chaque nouveau projet que nous avons commencé à exploiter au mieux le modèle relationnel que nous avons pu. Malheureusement, d'autres programmeurs/consultants de cette époque ont toujours conçu leurs bases de données comme s'ils travaillaient avec les anciennes contraintes de ce système.


Quelques exemples de ce que j'ai rencontré qui ne correspondaient pas au modèle relationnel:

  • Les dates ont été stockées sous la forme nombres de jours juliens qui nécessitait une jointure à une table de dates pour obtenir la date réelle.
  • Tableaux dénormalisés avec des colonnes séquentielles du même type (par exemple code1,code2, ..., code20)
  • NxM colonnes CHAR de longueur représentant un tableau de N chaînes de longueur M.

Les raisons qui m'ont été données pour ces décisions de conception étaient toutes basées sur les contraintes du système lors de la conception de la base de données.

Dates - On m'a dit qu'il fallait plus de temps de traitement pour utiliser les fonctions de date (quel mois ou jour ou jour de semaine) pour traiter une date que pour créer un tableau de toutes les dates possibles avec toutes ces informations.

Colonnes séquentielles du même type - L'environnement de programmation dans lequel ils se trouvaient permettait à un programme de créer une variable de tableau sur une partie de la ligne. Et c'était un moyen plus facile de réduire le nombre d'opérations de lecture.

Colonnes CHAR de longueur NxM - Il était plus facile de placer les valeurs de configuration dans une seule colonne pour réduire les opérations de lecture de fichiers.

Un exemple mal conçu en équivalent C pour refléter l'environnement de programmation dont ils disposaient:

#define COURSE_LENGTH 4
#define NUM_COURSES 4
#define PERIOD_LENGTH 2

struct mytable {
    int id;
    char periodNames[NUM_COURSES * PERIOD_LENGTH];  // NxM CHAR Column
    char course1[COURSE_LENGTH];
    char course2[COURSE_LENGTH];
    char course3[COURSE_LENGTH];
    char course4[COURSE_LENGTH];
};

...

// Example row
struct mytable row = {.id= 1, .periodNames="HRP1P2P8", .course1="MATH", .course2="ENGL", .course3 = "SCI ", .course4 = "READ"};

char *courses; // Pointer used to access the sequential columns
courses = (char *)&row.course1;


for(int i = 0; i < NUM_COURSES; i++) {

    printf("%d: %.*s -> %.*s\n",i+1, PERIOD_LENGTH, &row.periodNames[PERIOD_LENGTH * i], COURSE_LENGTH,&courses[COURSE_LENGTH*i]);
}

Les sorties

1: HR -> MATH
2: P1 -> ENGL
3: P2 -> SCI
4: P8 -> LIRE

D'après ce que l'on m'a dit, une partie de cela était considérée comme la meilleure pratique à l'époque.

3
Core.B