web-dev-qa-db-fra.com

Comment concevoir une base de données pour stocker une liste triée?

Je cherche à stocker une liste triée dans une base de données. Je souhaite effectuer efficacement les opérations suivantes.

  1. Insert (x) - Insère l'enregistrement x dans le tableau
  2. Supprimer (x) - Supprimer l'enregistrement x du tableau
  3. Avant (x, n) - Renvoie les enregistrements "n" précédant l'enregistrement x dans la liste triée.
  4. Après (x, n) - Renvoie les enregistrements "n" succédant à l'enregistrement x dans la liste triée.
  5. First (n) - Renvoie les premiers 'n' enregistrements de la liste triée.
  6. Last (n) - Renvoie les derniers 'n' enregistrements de la liste triée.
  7. Comparer (x, y) - Étant donné deux enregistrements x et y du tableau, trouvez si x> y.

La méthode simple à laquelle je pourrais penser est de stocker une sorte d'attribut de "rang" dans la table et d'interroger en triant cet attribut. Mais dans cette méthode, insérer/modifier un enregistrement avec un rang devient une opération coûteuse. Existe-t-il une meilleure méthode?

Plus précisément, je cherche à implémenter la table en utilisant SimpleDB d'Amazon. Mais une réponse générale pour une base de données relationnelle devrait également être utile.

Mise à jour du profil de charge:

Étant donné que je prévois cela pour une application Web, cela dépend du nombre d'utilisateurs qui utilisent l'application.

S'il y a 100 000 utilisateurs actifs (super optimisme: P), alors mon estimation très approximative par jour serait

500 000 sélections, 100 000 insertions et suppressions, 500 000 mises à jour

Je m'attendrais à ce que la table atteigne 500k au total.

Je cherche à optimiser les mises à jour, les insertions et les opérations de comparaison. Le rang des articles changera constamment et je dois garder la table à jour.

44
chitti

Si le rang n'est pas complètement arbitraire mais est dérivé à la place d'une autre propriété (par exemple nom, score du joueur, etc.), alors jetez un coup d'œil à réponse de Joel .

Si elle est une propriété arbitraire de vos données, alors cela devrait être stocké sous forme de colonne dans votre table d'enregistrements. En supposant que SimpleDB d'Amazon est similaire au SGBDR typique, vous pouvez ensuite indexer cette colonne et satisfaire rapidement toutes vos requêtes ci-dessus avec la stratégie d'indexation appropriée. Ceci est normal pour un SGBDR.

Étant donné que vous vous attendez à une activité d'insertion et de mise à jour élevée, mais également à une activité de lecture relativement élevée, je recommande de procéder comme suit:

  • Regroupez la table sur le rang, surtout si la grande majorité de vos requêtes sont contre le rang. Si ce n'est pas le cas, ou si le choix d'une clé de clustering n'est pas disponible dans SimpleDB, créez simplement un index avec le rang comme colonne de tête. Cela satisferait les requêtes 3-6.
  • Un index sur l'enregistrement d'abord, puis sur le classement (ou, dans le monde SQL Server, enregistrez simplement et INCLUDE- classement, ou enregistrez simplement si vous avez groupé sur le classement) satisferait la requête 7.
  • Les opérations 1 et 2 peuvent être optimisées en espaçant correctement vos données (c'est-à-dire en définissant le FILLFACTOR dans SQL Server). Ceci est particulièrement important si vous vous regroupez sur le rang.
  • Lorsque vous insérez ou mettez à jour des classements, maintenez autant d'écart que possible entre les numéros de classement afin de minimiser la possibilité que vous ayez besoin de reclasser un enregistrement existant pour tenir compte d'une insertion ou d'une mise à jour de classement. Par exemple, si vous classez vos enregistrements par étapes de 1 000, vous laissez suffisamment de place pour environ la moitié du nombre de modifications et d'insertions avec un minimum de chances dont vous aurez besoin pour reclasser un enregistrement qui n'est pas directement impliqué dans ces changements.
  • Chaque nuit, reclassifiez tous les enregistrements pour réinitialiser les écarts de classement entre eux.
  • Vous pouvez régler la fréquence des reclassements en masse ainsi que la taille de l'écart de classement pour tenir compte du nombre prévu d'insertions ou de mises à jour par rapport au nombre d'enregistrements existants. Donc, si vous avez 100 000 enregistrements et que vos insertions et mises à jour en représentent 10%, laissez suffisamment de place pour 10 000 nouveaux classements et reclassifiez-les tous les soirs.
  • Le reclassement des enregistrements 500K est une opération coûteuse, mais cela doit être fait une fois par jour ou par semaine en dehors des heures normales pour une base de données comme celle-ci. Ce reclassement de masse en dehors des heures de travail pour maintenir les écarts de classement vous évite d'avoir à reclassifier de nombreux enregistrements pour chaque mise à jour ou insertion de classement pendant vos heures normales et de pointe.

Si vous vous attendez à 100K + lectures sur une table de taille 100K +, je ne recommande pas d'utiliser l'approche de liste liée. Il ne s'adaptera pas bien à ces tailles.

22
Nick Chammas

J'utilise généralement la méthode de "classement" que vous décrivez. Plutôt que de jouer avec la mise à jour des lignes lorsque les éléments doivent être réorganisés, j'ai souvent pu éviter de supprimer tous les enregistrements de la liste et de réinsérer de nouveaux éléments dans le bon ordre. Cette méthode est clairement optimisée pour la récupération.

Une autre approche consisterait à modéliser les enregistrements sous forme de liste chaînée en utilisant une colonne de clé étrangère réflexive "prédécesseur" sur la table:

ID   setID   item       predecessor
---  ------  ------     ------------
1    1       Apple      null
2    1       Orange     1
3    2       Cucumber   null
4    1       Pear       2
5    1       Grape      4
6    2       Carrot     3

Vous pouvez facilement récupérer une liste et ajouter et supprimer des éléments avec peu de frais généraux, mais il sera difficile de récupérer les enregistrements dans le bon ordre. Il existe peut-être un moyen intelligent de le faire dans une seule requête, probablement avec de nombreuses jointures de table aliasées.

J'utilise cette dernière approche souvent lorsque je modélise une relation arborescente (catégories, dossiers, ensembles et sous-ensembles). J'ai généralement eu une fonction récursive d'une certaine sorte pour reconstruire l'arborescence complète dans mon application.

13
bpanulla

Je pense que la chose à faire est de stocker la propriété ou les propriétés qui sont utilisées pour calculer le classement puis construire un index sur eux. Plutôt que d'essayer de forcer la base de données à stocker physiquement les données dans l'ordre de classement ou d'utiliser une liste de liens gérée manuellement, pourquoi ne pas laisser le moteur de base de données faire ce pour quoi il a été conçu?

6
Joel Brown

Ce sont les limites d'un non-SGBDR comme simpleDB. Les fonctionnalités dont vous avez besoin ne peuvent pas être implémentées côté DB dans simpleDB, elles doivent être implémentées depuis le côté programmation/application.

Pour un SGBDR comme SQL server, les fonctionnalités dont vous avez besoin sont rudimentaires à l'index clusterisé.

  • Insert (x) - Insérez l'enregistrement x dans le tableau> Insertion simple.
  • Supprimer (x) - Supprimer l'enregistrement x du tableau> Suppression simple.
  • Avant (x, n) - Renvoie les enregistrements "n" précédant l'enregistrement x dans la liste triée. > Sélectionnez les n premiers résultats où x est inférieur à la valeur et classez par clause.

  • Après (x, n) - Renvoie les enregistrements "n" succédant à l'enregistrement x dans la liste triée. > Sélectionnez les n premiers résultats où x est supérieur à la valeur et classez par clause.

  • First (n) - Renvoie les premiers 'n' enregistrements de la liste triée. > Sélectionnez les n premiers résultats.

  • Last (n) - Renvoie les derniers 'n' enregistrements de la liste triée. > Sélectionnez les n premiers résultats après ordre par desc.

  • Comparer (x, y) - Étant donné deux enregistrements x et y du tableau, trouvez si x> y. > Instruction TSQL IF.
1
StanleyJohns

Voici ce que j'ai utilisé pour re-classer ma table Postgres après chaque insertion:

CREATE OR REPLACE FUNCTION re_rank_list() RETURNS trigger AS $re_rank_list$
DECLARE
    temprow record;
    row_idx integer := 1;    
BEGIN
    FOR temprow IN
    SELECT * FROM your_schema.your_list WHERE list_id = NEW.list_id ORDER BY rank ASC
    LOOP
        UPDATE your_schema.your_list SET rank = row_idx * 100 WHERE id = temprow.id;
        row_idx := row_idx + 1;
    END LOOP;
    RETURN NEW;
END;
$re_rank_list$ LANGUAGE plpgsql;


CREATE TRIGGER re_rank_list AFTER UPDATE ON your_schema.your_list_value
    FOR EACH ROW 
    WHEN (pg_trigger_depth() = 0)
    EXECUTE PROCEDURE re_rank_list();

Pour mon cas d'utilisation, les performances ne sont pas un problème, mais il est important d'avoir confiance qu'elles ne se briseront jamais ou n'agiront pas bizarrement.

0
Mark