Conception de la base de données - Stocker l'état ou calculer l'état à chaque fois?

17

Disons que j'ai une application de base de données relationnelle et un objet "utilisateur" et un objet "message". Maintenant, je veux montrer le nombre de messages non lus à cet utilisateur.

Quelle est la meilleure façon d'archiver cela? Dois-je introduire un champ dans l'utilisateur et le compter si l'utilisateur reçoit un message et diminuer le nombre s'il en lit un? Ou dois-je exécuter une requête à chaque fois pour calculer le nombre de messages pour l'utilisateur qui sont marqués comme non lus?

Je pense que la première approche est plus compliquée et sujette aux erreurs, mais fonctionnera mieux que la deuxième approche.

Comment cela se fait-il normalement ou quelle est la meilleure approche?

Jan
la source
1
Cela dépend d'un certain nombre de facteurs: votre base de données est-elle partitionnée? Combien de lignes / utilisateur attendez-vous? Quelle taille de base de données attendez-vous (ou combien d'utilisateurs au total)? Combien de demandes attendez-vous par seconde? Tout cela n'a pas besoin d'être précis, mais quelques idées grossières ...
Omer Iqbal
10
+1 Il s'agit d'une question de base de données relationnelle classique. Normaliser ou ne pas normaliser? Telle est la question. Que tu sois plus noble dans le schéma pour subir les frondes et les flèches de la duplication scandaleuse, ou prendre des déclencheurs, et en les employant, y mettre fin?
Ross Patterson
Je soutiens que s'il s'agit d'un Rel classique. db. question, il devrait déjà y avoir une réponse sur le site, cela devrait être fermé comme DUP, ou nous n'avons pas de réponse et cela devrait être laissé ouvert.
mattnz

Réponses:

14

Comment cela se fait-il normalement ou quelle est la meilleure approche?

La meilleure approche consiste à l'essayer d'abord sans champ supplémentaire, à mesurer les performances, et si cela s'avère vraiment trop lent, vous essayez d'optimiser. Cela pourrait signifier passer à votre première approche en utilisant un champ supplémentaire, mais vous devriez également envisager de tester d'autres options, par exemple, en mettant un index supplémentaire sur les champs combinés ("non lus", "ID utilisateur") sur vos messages.

Doc Brown
la source
2
La meilleure approche consiste à (utiliser d'abord la méthode la plus simple). Les règles générales sont meilleures que les détails, fwiw. (+1 pour "test!" Cependant.)
DougM
9

La solution de manuel selon la théorie de la base de données serait de n'avoir aucune valeur dans votre base de données qui dépende des valeurs d'autres données, car ce sont des dépendances transitives . Avoir des champs qui sont des valeurs calculées basées sur d'autres champs est une violation de la normalisation, car cela conduit à des informations redondantes.

Cependant, parfois ce que dit le manuel et quelle est la méthode la plus pratique dans la pratique diffèrent. Compter le nombre de messages non lus chaque page vue pourrait être une opération assez coûteuse. La mise en cache du nombre dans la usertable serait bien meilleure pour les performances. Le coût serait qu'il pourrait y avoir des incohérences dans la base de données: il pourrait être possible qu'un message soit supprimé, ajouté ou lu sans se rappeler de mettre également à jour le compteur non lu.

Philipp
la source
4
Le problème de cohérence est facile à lécher avec des déclencheurs qui ajustent le compteur sur INSERTou DELETE. (Ou UPDATE, pour tenir compte du changement de propriétaire d'un message.). Un bon SGBD fera l'opération et exécutera les déclencheurs dans la même transaction, donc tout ou rien se produira.
Blrfl
4

Le problème potentiel est la performance et vous n'avez pas encore de problème de performance. Il y a beaucoup de choses que vous pouvez faire en fonction de la base de données de votre choix pour gérer cela dans la solution # 1: indexation, matériel, mise en cache, etc. Tout dépend de la fréquence à laquelle l'utilisateur a besoin pour obtenir un nombre de messages non lus en cours. Beaucoup de ces choix ne nécessitent pas de codage personnalisé du côté de l'application, vous pouvez donc les implémenter avec un changement de code ou très peu. Facilite la croissance avec l'application.

Une fois qu'un utilisateur se connecte / se connecte, obtenir le décompte de la base de données une fois n'est pas si mal. Votre application conservera-t-elle une liste de messages constamment mise à jour comme les e-mails? Obtenir un décompte non lu à partir d'ici ne nécessite pas un autre voyage dans la base de données et pour obtenir de nouveaux messages, il faudra quand même faire un voyage db.

Faire un voyage vers la base de données à chaque fois qu'un message est lu pour signaler IsRead? champ suffit sans recalculer un autre champ.

Avec la solution n ° 2 (garder un décompte dans un champ / sur disque), aurez-vous besoin d'une routine pour reconstruire / recalculer périodiquement ce champ en cas de problème? Et il y a toujours des problèmes. Allez-vous envelopper tout cela dans une transaction? Chaque fois que quelqu'un envoie un message à quelqu'un d'autre, il peut échouer car il ne peut pas mettre à jour le UnreadCount de l'utilisateur destinataire en raison d'un verrouillage de la table User? Ou allez-vous créer une table distincte pour ce champ?

JeffO
la source
+1 pour avoir mentionné les problèmes de performances liés à la mise à jour des champs de comptage
winkbrace
0

La façon dont je le ferais est d'exécuter une requête à chaque fois, c'est-à-dire votre deuxième approche. Assurez-vous simplement d'ajouter un index dans votre table de messages sur la colonne qui agit comme une clé étrangère dans la table des utilisateurs pour améliorer les performances de votre requête.

Ensuite, comme le dit Doc, mesurez les performances de cette approche et vous pourrez alors dire si vous devez prendre un chemin différent.

Jose B
la source