Je fais des recherches dans les bases de données et j'examine certaines limites des bases de données relationnelles.
Je me rends compte que les jointures de grandes tables coûtent très cher, mais je ne sais pas trop pourquoi. Que doit faire le SGBD pour exécuter une opération de jointure? Où se trouve le goulot d'étranglement?
Comment la dénormalisation peut-elle aider à surmonter cette dépense? Comment les autres techniques d'optimisation (indexation, par exemple) aident-elles?
Les expériences personnelles sont les bienvenues! Si vous souhaitez publier des liens vers des ressources, évitez Wikipedia. Je sais déjà où trouver ça.
Par rapport à cela, je m'interroge sur les approches dénormalisées utilisées par les bases de données de services cloud telles que BigTable et SimpleDB. Voir cette question .
Dénormaliser pour améliorer les performances? Cela semble convaincant, mais ça ne tient pas la route.
Chris Date, qui, avec le Dr Ted Codd, était à l'origine du modèle de données relationnel, a perdu patience avec les arguments mal informés contre la normalisation et les a systématiquement démolis à l'aide d'une méthode scientifique: il a obtenu de grandes bases de données et a testé ces assertions.
Je pense qu'il l'a écrit dans base de données relationnelle Writings 1988-1991 mais ce livre a par la suite été intégré à la sixième édition de . Introduction aux systèmes de base de données , qui est le texte définitif sur la théorie et la conception de base de données, dans sa huitième édition au moment où j'écris et susceptible de rester imprimé pendant des décennies. Chris Date était un expert dans ce domaine alors que la plupart d'entre nous courions encore pieds nus.
Il a trouvé que:
Tout revient à atténuer la taille de l'ensemble de travail. Les jointures impliquant des clés correctement sélectionnées avec des index correctement configurés sont économiques, car elles permettent un élagage significatif du résultat avant que les lignes ne soient matérialisées.
La matérialisation du résultat implique des lectures de disque en vrac, qui représentent par ordre de grandeur l’aspect le plus coûteux de l’exercice. Au contraire, l’exécution d’une jointure nécessite logiquement la récupération des seules touches . En pratique, même les valeurs de clé ne sont pas récupérées: les valeurs de hachage de clé sont utilisées pour les comparaisons de jointures, ce qui permet de réduire le coût des jointures multi-colonnes et de réduire radicalement le coût des jointures impliquant des comparaisons de chaînes. Non seulement la mémoire cache sera mieux adaptée, mais il y aura beaucoup moins de lecture de disque à faire.
De plus, un bon optimiseur choisira la condition la plus restrictive et l’appliquera avant de réaliser une jointure, exploitant très efficacement la sélectivité élevée des jointures sur des index à cardinalité élevée.
Certes, ce type d'optimisation peut également s'appliquer aux bases de données dénormalisées, mais le type de personnes qui veulent dénormaliser un schéma ne pense généralement pas à la cardinalité lorsque (si) ils ont mis en place des index.
Il est important de comprendre que les balayages de table (l'examen de chaque ligne d'un tableau au cours de la création d'une jointure) sont rares dans la pratique. Un optimiseur de requêtes choisira une analyse de table uniquement quand un ou plusieurs des cas suivants sont en attente.
Effectuer une opération coûte plus cher que ne pas la réaliser. Cependant, effectuer l'opération incorrecte , être forcé dans une E/S disque inutile, puis abandonner les scories avant d'effectuer la jointure dont vous avez réellement besoin, est beaucoup plus cher. Même lorsque la "mauvaise" opération est précalculée et que les indices ont été judicieusement appliqués, il subsiste une pénalité importante. Dénormaliser pour précalculer une jointure - nonobstant les anomalies de mise à jour impliquées - est un engagement envers une jointure particulière. Si vous avez besoin d'un différent rejoindre, cet engagement va vous coûter cher grand .
Si quelqu'un veut me rappeler que le monde est en train de changer, je pense que vous constaterez que des jeux de données plus volumineux sur du matériel plus sophistiqué ne font qu'exagérer la dispersion des résultats de Date.
Pour tous ceux qui travaillent sur des systèmes de facturation ou des générateurs de courrier indésirable (honte à vous) et qui s'indignent sur le clavier pour me dire que vous savez pertinemment que la dénormalisation est plus rapide, désolée mais que vous vivez dans l'un des cas - en particulier le cas où vous traitez toutes les données , dans l'ordre. Ce n'est pas un cas général, et vous êtes justifié dans votre stratégie.
Vous êtes pas en droit de le généraliser faussement. Voir la fin de la section de notes pour plus d'informations sur l'utilisation appropriée de la dénormalisation dans des scénarios d'entreposage de données.
J'aimerais aussi répondre à
Les joints sont juste des produits cartésiens avec un peu de brillant à lèvres
Quelle charge de bêtises. Les restrictions sont appliquées le plus tôt possible, le plus restrictif en premier. Vous avez lu la théorie, mais vous ne l'avez pas comprise. Les jointures sont traitées comme "produits cartésiens auxquels s'appliquent les prédicats" uniquement par l'optimiseur de requête. Il s’agit d’une représentation symbolique (une normalisation, en fait) destinée à faciliter la décomposition symbolique de sorte que l’optimiseur puisse produire toutes les transformations équivalentes et les classer par coût et par sélectivité, de manière à pouvoir sélectionner le meilleur plan de requête.
La seule façon pour que l'optimiseur produise un produit cartésien est de ne pas fournir de prédicat: SELECT * FROM A,B
David Aldridge fournit des informations supplémentaires importantes.
Outre les index et les analyses de table, il existe en effet diverses autres stratégies. Un optimiseur moderne leur coûtera tout avant de produire un plan d'exécution.
Un conseil pratique: si elle peut être utilisée comme clé étrangère, indexez-la pour qu'une stratégie d’indexation soit disponible pour l’optimiseur.
J'étais plus intelligent que l'optimiseur MSSQL. Cela a changé il y a deux versions. Maintenant, il m'apprend généralement me . C'est en réalité un système expert codifiant toute la sagesse de nombreuses personnes très intelligentes dans un domaine suffisamment fermé pour qu'un système basé sur des règles soit efficace.
"Bollocks" a peut-être manqué de tact. On me demande d’être moins hautaine et je rappelle que les mathématiques ne mentent pas. C'est vrai, mais toutes les implications des modèles mathématiques ne doivent pas nécessairement être prises à la lettre. Les racines carrées des nombres négatifs sont très utiles si vous évitez soigneusement d'examiner leur absurdité (jeu de mots là-bas) et assurez-vous de les annuler avant d'essayer d'interpréter votre équation.
La raison pour laquelle j’ai répondu si sauvagement est que la déclaration telle qu’elle est libellée dit que
Les jointures sont des produits cartésiens ...
Ce n'est peut-être pas ce que l'on voulait dire, mais c'est ce qui a été écrit et c'est absolument faux. Un produit cartésien est une relation. Une jointure est une fonction. Plus précisément, une jointure est une fonction relationnelle. Avec un prédicat vide, il produira un produit cartésien. La vérification de l'exactitude d'un moteur de requête de base de données est vérifiée, mais personne n'écrit des jointures non contraintes dans la pratique car elles n'ont aucune valeur pratique en dehors d'une salle de classe.
J'ai appelé cela parce que je ne voulais pas que les lecteurs tombent dans le vieux piège de confondre le modèle avec la chose modelée. Un modèle est une approximation, volontairement simplifiée pour une manipulation pratique.
La date limite de sélection d'une stratégie de jointure de table-scan peut varier selon les moteurs de base de données. Il est affecté par un certain nombre de décisions d'implémentation, telles que le facteur de remplissage des noeuds d'arborescence, la taille des valeurs clés et les subtilités de l'algorithme, mais en gros, l'indexation hautes performances a un temps d'exécution de k log n + c . Le terme C est une surcharge fixe principalement constituée de temps d’installation, et la forme de la courbe signifie que vous n’obtenez aucun gain (comparé à une recherche linéaire) avant n est dans les centaines.
La dénormalisation est un engagement envers une stratégie de jointure particulière. Comme mentionné précédemment, cela interfère avec d'autres stratégies de jointure . Mais si vous avez des compartiments d’espace disque, des modèles d’accès prévisibles et une tendance à en traiter une grande partie ou la totalité, le calcul préalable d’une jointure peut s'avérer très utile.
Vous pouvez également déterminer les chemins d'accès que votre opération utilise généralement et précalculer toutes les jointures pour ces chemins d'accès. C'est la prémisse derrière les entrepôts de données, ou du moins quand ils sont construits par des personnes qui savent pourquoi ils font ce qu'ils font, et pas seulement par souci de conformité aux mots à la mode.
Un entrepôt de données correctement conçu est produit périodiquement par une transformation en bloc à partir d'un système de traitement de transaction normalisé. Cette séparation des bases de données des opérations et des rapports a l’effet très souhaitable d’éliminer le conflit entre OLTP et OLAP (traitement des transactions en ligne, par exemple, saisie de données et analyse analytique en ligne)). traitement, c'est-à-dire rapports).
Un point important ici est que, mis à part les mises à jour périodiques, l'entrepôt de données est en lecture seule . Cela rend inutile la question des anomalies de mise à jour.
Ne commettez pas l’erreur de dénormaliser votre base de données OLTP (la base de données sur laquelle la saisie de données a lieu). Cela pourrait être plus rapide pour les opérations de facturation, mais si vous le faites, vous obtiendrez des anomalies de mise à jour. obtenir du Reader's Digest d'arrêter de vous envoyer des trucs?
L'espace disque est bon marché de nos jours, alors assommez-vous. Mais la dénormalisation n'est qu'une partie de l'histoire des entrepôts de données. Des gains de performance beaucoup plus importants sont dérivés des valeurs cumulées précalculées: totaux mensuels, ce genre de choses. Il est toujours question de réduire l’ensemble de travail.
Supposons que vous disposiez d'une table SQL Server contenant une colonne indexée de type varchar et que vous utilisiez AddWithValue pour transmettre un paramètre contraignant une requête sur cette colonne. Les chaînes C # sont en Unicode, le type de paramètre inféré sera donc NVARCHAR, qui ne correspond pas à VARCHAR.
VARCHAR to NVARCHAR est une conversion en expansion, donc cela se produit de manière implicite - mais dites adieu à l'indexation et bonne chance pour comprendre pourquoi.
Si tout est mis en cache dans la RAM, JOINs
sont plutôt bon marché. C'est-à-dire que la normalisation n'a pas beaucoup de pénalité de performance .
Si un schéma "normalisé" fait que JOINs
frappe beaucoup le disque, mais que le schéma "dénormalisé" équivalent ne soit pas obligé de frapper le disque, la dénormalisation gagne une compétition de performances.
Commentaire de l'auteur original: Les moteurs de base de données modernes sont très efficaces pour organiser le séquencement des accès afin de minimiser les erreurs de cache lors des opérations de jointure. Ce qui précède, bien que vrai, pourrait être confondu avec le sens que les jointures sont nécessairement coûteuses pour des données volumineuses. Cela conduirait à une mauvaise prise de décision de la part de développeurs inexpérimentés.
Ce que la plupart des commentateurs ne notent pas, c’est le large éventail de méthodologies de jonction disponibles dans un SGBDR complexe, et les dénormaliseurs occultent invariablement le coût plus élevé de la maintenance des données dénormalisées. Toutes les jointures ne sont pas basées sur des index et les bases de données disposent de nombreux algorithmes optimisés et méthodologies de jointure destinés à réduire les coûts de jointure.
Dans tous les cas, le coût d'une jointure dépend de son type et de quelques autres facteurs. Cela n’a pas besoin d’être cher - quelques exemples.
Les bases de données sont conçues pour être jointes et sont très souples dans leur manière de procéder et généralement très performantes, à moins que le mécanisme de jointure ne soit incorrect.
Je pense que toute la question est basée sur une fausse prémisse. Les jointures sur de grandes tables sont non nécessairement coûteuses. En fait, faire des jointures est l'une des principales raisons pour lesquelles les bases de données relationnelles existent du tout. Les jointures sur de grands ensembles sont souvent coûteuses, mais vous souhaitez très rarement joindre tout le contenu de la grande table A à celui de la grande table B. Au lieu de cela, vous écrivez la requête de sorte que seules les lignes importantes de chaque table soient utilisées et que le jeu réel conservé par la jointure reste plus petit.
De plus, vous bénéficiez des rendements mentionnés par Peter Wone, de sorte que seules les parties importantes de chaque enregistrement doivent être en mémoire jusqu'à ce que le jeu de résultats final soit matérialisé. De même, dans les requêtes volumineuses comportant de nombreuses jointures, vous souhaitez généralement commencer avec les jeux de tables les plus petits et progresser jusqu'aux plus gros, de sorte que le jeu conservé en mémoire reste aussi petit que possible et aussi longtemps que possible.
Lorsqu'elles sont correctement effectuées, les jointures constituent généralement le meilleur moyen de comparer, combiner ou filtrer de grandes quantités de données.
Le goulot d'étranglement est à peu près toujours Entrée/sortie disque, et plus précisément encore - Entrée/sortie disque aléatoire (par comparaison, les lectures séquentielles sont assez rapides et peuvent être mises en cache avec des stratégies de lecture anticipée).
Les jointures peut augmenter le nombre de recherches aléatoires - si vous vous aventurez à lire de petites parties d’un grand tableau. Mais les optimiseurs de requêtes le recherchent et le transformeront en une analyse de table séquentielle (en supprimant les lignes inutiles) s’il pense que ce serait mieux.
Un seul tableau dénormalisé a un problème similaire: les lignes sont volumineuses et tiennent donc moins bien sur une seule page de données. Si vous avez besoin de lignes éloignées les unes des autres (et que leur grande taille les sépare), vous aurez plus d'E/S aléatoires. Encore une fois, une analyse de table peut être forcée pour éviter cela. Mais, cette fois, votre table doit lire plus de données à cause de la grande taille des lignes. Ajoutez à cela le fait que vous êtes en train de copier des données d'un emplacement unique vers plusieurs emplacements, et le SGBDR en a beaucoup plus à lire (et à mettre en cache).
Avec 2 tables, vous obtenez également 2 index clusterisés - et pouvez généralement indexer davantage (en raison de moins de temps d’insertion/de mise à jour), ce qui peut vous permettre d’obtenir des performances considérablement accrues (principalement, encore une fois, car les index sont (relativement) petits, rapides à lire sur le disque. (ou peu coûteux à mettre en cache) et réduisez le nombre de lignes de la table que vous devez lire à partir du disque).
À propos de l'unique surcharge avec une jointure vient de déterminer les lignes correspondantes. Sql Server utilise 3 types de jointures différents, principalement basés sur la taille des ensembles de données, pour rechercher les lignes correspondantes. Si l'optimiseur sélectionne le type de jointure incorrect (en raison de statistiques inexactes, d'index inadéquats ou simplement d'un bogue d'optimiseur ou d'un cas Edge), il peut affecter considérablement les temps de requête.
Dans le cas optimal, ils ne provoquent aucune entrée/sortie de disque et sont donc négligeables du point de vue des performances.
Au total, au pire, il devrait être plus rapide de lire la même quantité de logique données de x tables jointes, comme c'est le cas d'une seule table dénormalisée en raison des lectures plus réduites du disque. Pour lire la même quantité de données physiques, il pourrait y avoir une légère surcharge.
Étant donné que le temps de requête est généralement dominé par les coûts d'E/S et que la taille de vos données ne change pas (avec une charge de ligne très minime) avec la dénormalisation, la fusion de tableaux ne présente pas un avantage énorme. Le type de dénormalisation qui tend à augmenter les performances, IME, met en cache les valeurs calculées au lieu de lire les 10 000 lignes nécessaires à leur calcul.
L'ordre dans lequel vous rejoignez les tables est extrêmement important. Si vous avez deux ensembles de données, essayez de construire la requête de manière à ce que le plus petit soit utilisé en premier pour réduire la quantité de données sur laquelle la requête doit fonctionner.
Cela n'a pas d'importance pour certaines bases de données, par exemple, MS SQL connaît la plupart du temps le bon ordre de jointure. Pour certains (comme IBM Informix), l'ordre fait toute la différence.
Décider de la normalisation ou de la normalisation est un processus assez simple lorsque vous considérez la classe de complexité de la jointure. Par exemple, j'ai tendance à concevoir mes bases de données avec une normalisation lorsque les requêtes sont O (k log n) où k est relatif à la grandeur de sortie souhaitée.
Un moyen simple de dénormaliser et d’optimiser les performances est d’expliquer comment les modifications apportées à votre structure de normalisation affectent votre structure dénormalisée. Cela peut toutefois être problématique car cela peut nécessiter une logique transactionnelle pour travailler sur une structure dénormalisée.
Le débat sur la normalisation et la dénormalisation ne va pas se terminer car les problèmes sont vastes. Il existe de nombreux problèmes pour lesquels la solution naturelle nécessite les deux approches.
En règle générale, j'ai toujours stocké une structure normalisée et des caches dénormalisés pouvant être reconstruits. Finalement, ces caches me sauvent le cul pour résoudre les futurs problèmes de normalisation.