Compromis entre normalisation et dénormalisation

Voir la différence entre les schémas normalisés et dénormalisés pour la modélisation des données, et certains des compromis avec chacun.

Normalisation

La normalisation est une façon de définir le schéma de votre base de données d’une manière optimisée pour une writes rapide et à haute intégrité en garantissant l’absence de données redondantes à travers les tables.

La norme pour la normalisation est de tirer pour 3rd normal form (3nf).

Un bref rappel des critères pour atteindre 3nf :

  • Les enregistrements d’une table doivent contenir des clés_primaires qui les référencent de manière unique (1nf)

  • Il ne doit pas y avoir de colonnes répétées dans une table (1nf)

  • Il ne doit pas y avoir de ne devrait pas y avoir de clés fonctionnellement dépendantes (2nf)

  • Il ne devrait pas y avoir de clés fonctionnellement dépendantes transitoires (3nf)

Puisque les tableaux ne contiennent pas de données redondantes, le storage nécessaire pour les nouveaux enregistrements de toute table est relativement faible.

Puisque les écritures sont petites, elles sont également fast.

Les écritures sont également garanties pour laisser la base de données dans un consistent état, en raison des referential integrity garanties des contraintes de clé étrangère entre les tables liées.

Dénormalisation

La dénormalisation est le fait d’ajouter des redondances ou des valeurs dérivées dans votre schéma pour optimiser reads ce qui serait autrement coûteux dans un schéma normalisé.

Pensez à une base de données entièrement normalisée pendant une minute…

Tout est dans sa propre table…

Tout a sa propre clé primaire…

Les références entre les tables sont maintenues par des contraintes de clé étrangère…

… c’est génial d’un point de vue stockage et intégrité, mais cela peut conduire à ce que des morceaux d’une requête soient répartis sur de nombreuses tables, ce qui conduit à des jointures lentes et complexes pour obtenir l’image complète pour une requête.

Si vous êtes en mesure d’anticiper les types de requêtes que vos utilisateurs pourraient effectuer, il pourrait être judicieux de stocker certaines valeurs de manière redondante dans votre système afin d’accélérer les performances des requêtes.

L’ajout de valeurs dénormalisées rend les insertions et les mises à jour plus délicates : vous devez vous assurer que les valeurs dénormalisées sont correctement maintenues, car l’intégrité de ces valeurs n’est pas automatiquement appliquée par le schéma.

Le choix de dénormaliser doit être fait consciemment. Il doit être documenté, testé et doit être communiqué à l’ensemble d’une équipe afin que tous soient conscients de cette considération supplémentaire lors de l’écriture dans des tables dénormalisées.

Exemple normalisé

Je construis une application appelée CashBackHero, qui implique la modélisation des relations entre les cartes de crédit et les utilisateurs à travers une entité appelée portefeuille. Un schéma 3nf normalized pour ces entités pourrait ressembler à quelque chose comme ceci :

CashBack

.

id valeur
1 3
2 4
3 5

Cartes

.

id name cash_back_id
1 Chasse 2
2 Découverte 1
3 Amex 3

Portefeuilles

.

id nom
1 Portefeuille de Brian
2 Portefeuille d’Alex
3 Portefeuille de Lauren

CardWallets (associations entre portefeuilles et cartes)

.

.

.

id wallet_id card_id
1 1 1
2 1 3
3 2 2
4 3 2
5 3 1

Les tableaux ci-dessus sont normalized, car elles ne contiennent pas de données redondantes. Les relations entre les tables sont maintenues par des foreign_key contraintes vers d’autres tables. Cela permet de stocker les données dans leur forme la plus compact.

L’absence de données redondantes optimise également les writes vers la base de données. Les écritures les plus fréquentes sont les ajouts et les suppressions de la table CardWallets, ce qui implique juste des colonnes id qui contiennent des références à d’autres tables contenant des informations.

La normalisation des données rend également les mises à jour très faciles puisque chaque table est une source unique de vérité pour les informations qu’elle contient.

Une base de données normalisée optimise également certains types de lectures, comme le fait de surfacer une liste de toutes les valeurs d’une table particulière (comme obtenir toutes les cartes).

Les lectures où les données se trouvent dans plusieurs tables, cependant, deviennent plus difficiles. Une requête sur le schéma normalisé ci-dessus pour obtenir les noms et les valeurs de cash back des cartes du portefeuille 1 implique de joindre les tables Wallets et Cards à la table CardWallets, puis en joignant la table CashBackValue à la table Cards. Si ces tableaux deviennent volumineux, les requêtes pourraient devenir lentes par rapport à une version dénormalisée où toutes les données vivent dans un seul tableau.

En parlant de dénormalisation…

Exemple dénormalisé

Voyez à quoi ressemblerait le schéma normalisé présenté ci-dessus s’il était entièrement dénormalisé :

CashBackSchema

.

. Brian

.

id nom_de_carte nom_de_wallet valeur_de_back
1 Chase Portefeuille de Brian 4
2 Amex Portefeuille de Brian 2 5
3 Découverte Portefeuille d’Alex 3 4 Découverte Portefeuille de Lauren 3
5 Chasse Portefeuille de Lauren 4

C’est une table monstrueuse !

Ok… ça ne fait que 5 lignes… mais vous comprenez l’idée qu’une table dénormalisée peut souvent être plus facile à lire, puisque toutes les données existent au même endroit.

Au lieu de devoir joindre 4 tables ensemble pour obtenir les cartes et la valeur cash_back_value pour le portefeuille d’un utilisateur, nous pouvons maintenant simplement exécuter une instruction select vers la table CashBackSchema ci-dessus.

La recherche de cartes uniques devient un peu plus lente, car il faut lire l’ensemble du tableau pour déterminer les noms de cartes uniques.

Les mises à jour sont également un peu plus délicates : nous devons assurer l’intégrité de nos données logically. Par exemple, si nous voulons mettre à jour la valeur cash_back_value de la carte Chase dans le portefeuille de Brian, nous devons également nous demander si nous devons également mettre à jour la valeur cash_back_value de la carte Chase dans le portefeuille de Lauren. Les mises à jour doivent être effectuées à plusieurs endroits, ce qui peut être lent pour les grandes tables.

Dans un schéma normalisé, les mises à jour de quelque chose comme « wallet_display_name » peuvent juste être effectuées à un seul endroit (la table Wallets), au lieu de devoir passer au peigne fin toute la CashBackSchema pour s’assurer que chaque ligne avec le nom « Brian’s Wallet » est mise à jour de façon appropriée.