Wie entwerfe ich eine Datenbank zum Speichern einer sortierten Liste?

42

Ich möchte eine sortierte Liste in einer Datenbank speichern. Ich möchte die folgenden Vorgänge effizient ausführen.

  1. Einfügen (x) - Fügen Sie den Datensatz x in die Tabelle ein
  2. Löschen (x) - Löscht den Datensatz x aus der Tabelle
  3. Vorher (x, n) - Die 'n' Datensätze vor dem Datensatz x in der sortierten Liste zurückgeben.
  4. Nach (x, n) - Die 'n' Datensätze zurückgeben, die dem Datensatz x in der sortierten Liste folgen.
  5. First (n) - Gibt die ersten n Datensätze aus der sortierten Liste zurück.
  6. Last (n) - Gibt die letzten n Datensätze aus der sortierten Liste zurück.
  7. Compare (x, y) - Wenn zwei Datensätze x und y aus der Tabelle gegeben sind, finde, ob x> y ist.

Die einfache Methode, die ich mir vorstellen könnte, besteht darin, eine Art "Rang" -Attribut in der Tabelle zu speichern und durch Sortieren nach diesem Attribut abzufragen. Bei dieser Methode wird das Einfügen / Ändern eines Datensatzes mit Rang jedoch zu einer kostspieligen Operation. Gibt es eine bessere Methode?

Insbesondere möchte ich die Tabelle mit Amazon SimpleDB implementieren. Eine allgemeine Antwort für eine relationale Datenbank sollte jedoch ebenfalls hilfreich sein.

Update beim Ladeprofil:

Da ich dies für eine Webanwendung plane, hängt es von der Anzahl der Benutzer ab, die die App verwenden.

Wenn es 100.000 aktive Benutzer gibt (Superoptimismus: P), dann wäre meine sehr ungefähre Schätzung pro Tag

500.000 Auswahlen, 100.000 Einfügen und Löschen, 500.000 Aktualisierungen

Ich würde erwarten, dass der Tisch insgesamt auf 500.000 wächst.

Ich bin auf der Suche nach Updates, Insert und Compare-Operationen zu optimieren. Der Rang der Gegenstände wird sich ständig ändern und ich muss die Tabelle auf dem neuesten Stand halten.

Chitti
quelle
Erarbeiten Sie ein wenig Ihr erwartetes Lastprofil. Wie viele Selects / Inserts / Updates pro Tag? Für welche Vorgänge möchten Sie am meisten optimieren? Wie groß wird der Tisch voraussichtlich pro Tag oder insgesamt?
Nick Chammas
Gilt das für ein Spieler-Ranglisten-Board? Wie auch immer, ich habe meine Antwort unten mit Feedback basierend auf Ihrem geplanten Lastprofil aktualisiert.
Nick Chammas
nein, es ist kein Spielerranglisten-Brett.
Chitti
Welchen Ansatz haben Sie letztendlich gewählt?
Nick Chammas
Ich bin mir nicht mal sicher, was hier gefragt wird oder was Sie nicht tun müssen, aus der Wäscheliste der Dinge, die Sie tun müssen.
Evan Carroll

Antworten:

22

Wenn der Rang nicht völlig willkürlich ist, sondern von einer anderen Eigenschaft abgeleitet werden kann (z. B. Name, Punktzahl des Spielers usw.), schauen Sie sich Joels Antwort genau an .

Wenn es sich um eine willkürliche Eigenschaft Ihrer Daten handelt, sollte diese als Spalte in Ihrer Datensatztabelle gespeichert werden. Angenommen, Amazon SimpleDB ähnelt dem typischen RDBMS, können Sie diese Spalte indizieren und alle oben genannten Abfragen schnell mit der entsprechenden Indizierungsstrategie beantworten. Dies ist normal für ein RDBMS.

Da Sie eine hohe Einfüge- und Aktualisierungsaktivität, aber auch eine relativ hohe Leseaktivität erwarten, empfehle ich Folgendes:

  • Gruppieren Sie die Tabelle nach dem Rang, insbesondere wenn die meisten Ihrer Abfragen gegen den Rang sind. Wenn dies nicht der Fall ist oder wenn die Auswahl eines Clustering-Schlüssels in SimpleDB nicht verfügbar ist, erstellen Sie einfach einen Index mit Rang als führende Spalte. Dies würde die Fragen 3-6 erfüllen.
  • Ein Index, der zuerst den Datensatz und dann den Rang enthält (oder in der SQL Server-Welt nur den INCLUDERang " Aufzeichnen und -en" oder nur, wenn Sie nach Rang gruppiert haben), würde die Abfrage 7 erfüllen.
  • Die Vorgänge 1 und 2 können optimiert werden, indem Sie die Daten entsprechend verteilen (z. B. FILLFACTORin SQL Server festlegen). Dies ist besonders wichtig, wenn Sie sich nach Rang gruppieren.
  • Behalten Sie beim Einfügen oder Aktualisieren von Rängen so viel Abstand wie möglich zwischen den Rangnummern bei, um die Möglichkeit zu minimieren, dass Sie einen vorhandenen Datensatz neu einstufen müssen, um ein Einfügen oder Aktualisieren von Rängen zu ermöglichen. Wenn Sie beispielsweise Ihre Datensätze in 1000er-Schritten klassifizieren, bleibt genügend Platz für etwa die Hälfte der Änderungen und Einfügungen mit minimaler Wahrscheinlichkeit, und Sie müssen einen Datensatz, der nicht direkt an diesen Änderungen beteiligt ist, neu klassifizieren.
  • Jede Nacht werden alle Datensätze neu geordnet, um die Ranglücken zwischen ihnen zurückzusetzen.
  • Sie können die Häufigkeit der Massen-Neueinstufungen sowie die Ranglückengröße so einstellen, dass die erwartete Anzahl von Einfügungen oder Aktualisierungen im Verhältnis zur Anzahl der vorhandenen Datensätze berücksichtigt wird. Wenn Sie also über 100.000 Datensätze verfügen und davon ausgehen, dass Ihre Einfügungen und Aktualisierungen 10% davon ausmachen, lassen Sie genügend Platz für 10.000 neue Ränge und ordnen Sie sie jede Nacht neu.
  • Das erneute Einordnen von 500.000 Datensätzen ist eine kostspielige Operation, aber eine tägliche oder wöchentliche Unterbrechung der Arbeitszeit sollte für eine solche Datenbank in Ordnung sein. Diese Massen-Neueinstufung außerhalb der Geschäftszeiten, um die Ranglücken beizubehalten, erspart Ihnen die Neueinstufung vieler Datensätze für jede Rangaktualisierung oder -einfügung während Ihrer normalen Geschäftszeiten und Spitzenzeiten.

Wenn Sie mehr als 100.000 Lesevorgänge für eine Tabelle mit einer Größe von mehr als 100.000 erwarten, empfehle ich nicht, den Ansatz der verknüpften Liste zu verwenden. Es wird nicht gut auf diese Größen skaliert.

Nick Chammas
quelle
Die Ränge können geändert werden. Ich erwarte, dass sich die Reihen ständig ändern und ständig neue Datensätze eingefügt werden. Ich mache mir Sorgen um den Fall, dass ich ein neues Element mit einem Rang einfüge und dann die Ränge aller Datensätze unter dem neuen Datensatz in der Sortierreihenfolge ändern muss. Ist das nicht eine kostspielige Operation, wenn ich Tausende von Datensätzen in meiner Datenbank habe?
Chitti
@chitti - Ah, das ist ein Problem. Sie können Ihre Ranglisten ausräumen (z. B. 0, 1000, 2000, 3000, ...) und alle Datensätze in regelmäßigen Abständen neu ordnen, wenn sich die Ranglistenlücken füllen. Dies wird jedoch nicht skaliert, wenn Sie viel mehr als ein paar Zehntausend Datensätze erwarten.
Nick Chammas
1
@chitti - Das ist eigentlich ein bisschen lustig. Dies ist genau das Problem, mit dem Datenbank-Engines bei der Indizierung von Daten umgehen, da sie diese ordnen und neu ordnen, wenn Daten hinzugefügt oder geändert werden. Wenn Sie nachschlagen, werden FILLFACTORSie feststellen, dass es im Grunde genommen dazu gedacht ist, den zusätzlichen Platz für Datensätze in einem Index zu schaffen, genau wie die Ranglücken, die ich beschrieben habe, Platz für Rangänderungen und Einfügungen schaffen.
Nick Chammas
2
Danke für die aktualisierte Antwort. Der 'Rang' ist eine willkürliche Eigenschaft meiner Daten. Ich bin fast davon überzeugt, dass ich eine benutzerdefinierte Indexspalte benötige. Schauen Sie sich diesen SO-Link mit einer ähnlichen Frage an. Die obere Antwort enthält Empfehlungen zum Umgang mit einer solchen Rangspalte.
Chitti
@chitti - Die akzeptierte Antwort auf diese SO-Frage ist großartig. Es schlägt den gleichen Ansatz vor, den ich hier beschrieben habe, mit dem zusätzlichen Vorschlag, Dezimalstellen anstelle von Ganzzahlen zu verwenden, um Ihre Flexibilität beim Zuweisen und Ändern von Rängen erheblich zu erweitern. Toller Fund.
Nick Chammas
13

Ich verwende im Allgemeinen die von Ihnen beschriebene "Rang" -Methode. Anstatt mit dem Aktualisieren von Zeilen herumzuspielen, wenn Elemente neu angeordnet werden mussten, konnte ich häufig alle Datensätze in der Liste löschen und neue Elemente in der richtigen Reihenfolge einfügen. Diese Methode ist eindeutig für den Abruf optimiert.

Ein alternativer Ansatz wäre, die Datensätze als verknüpfte Liste zu modellieren, indem eine reflexive "Vorgänger" -Fremdschlüsselspalte in der Tabelle verwendet wird:

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

Sie können problemlos eine Liste abrufen und Elemente mit geringem Aufwand hinzufügen und entfernen. Es ist jedoch schwierig, die Datensätze in der richtigen Reihenfolge zu veröffentlichen. Vielleicht gibt es eine clevere Möglichkeit, dies in einer einzigen Abfrage zu tun, wahrscheinlich mit vielen Alias-Table-Joins.

Ich benutze diesen letzteren Ansatz oft, wenn ich eine baumartige Beziehung modelliere (Kategorien, Ordner, Mengen und Teilmengen). Im Allgemeinen hatte ich eine Art rekursive Funktion, um den gesamten Baum in meiner Anwendung zu rekonstruieren.

bpanulla
quelle
2
Das verknüpfte Listenmodell ist ordentlich. Um eine solche Hierarchie in SQL Server in der richtigen Reihenfolge abzurufen, verwenden Sie einen rekursiven CTE .
Nick Chammas
Für einen hohen Tisch wäre der Aufbau dieser Hierarchie allerdings ziemlich kostspielig. Der Vorteil ist, dass Rangänderungen / Einfügungen / usw. leicht vorgenommen werden können. Abhängig vom erwarteten Lastprofil von chitti ist dies möglicherweise der beste Ansatz.
Nick Chammas
Die Option für verknüpfte Listen scheint die beste Idee für alle Operationen außer Vergleichen zu sein. Irgendeine Idee, wie ich Compare implementieren würde, ohne den Pfad zwischen den beiden zu vergleichenden Elementen verfolgen zu müssen?
Chitti
Wenn Sie die IDs der Artikel haben, ist Compare () meiner Meinung nach unkompliziert, es sei denn, ich habe falsch verstanden, was Sie mit Compare () gemeint haben. Als Sie sagten: "finde ob x> y" meinten Sie "finde ob x vor y"? Ich kann mir nicht vorstellen, dass dies ohne einen benutzerdefinierten Index oder eine gespeicherte Prozedur, die die Liste durchgehen würde (oder ohne die von @Nick erwähnte interessante CTE-Funktion), einfach ist.
bpanulla
5
Diese Art von Lösung nähert sich auch einem Graphendatenmodell an ( en.wikipedia.org/wiki/Graph_theory ). Ein für die Speicherung von Diagrammknoten und Kanten optimiertes Speichersystem ist möglicherweise eine bessere Lösung als ein RDBMS. Triple- und Quad-Stores und Graph-Datenbanken wie Neo4J sind hier ziemlich gut.
bpanulla
6

Ich würde denken, dass die Sache zu tun ist, die Eigenschaft oder Eigenschaften zu speichern, die verwendet werden, um den Rang zu berechnen, und dann einen Index über sie zu erstellen. Anstatt zu versuchen, die Datenbank zu zwingen, die Daten physisch in einer Rangfolge zu speichern oder eine manuell verwaltete verknüpfte Liste zu verwenden, sollten Sie das Datenbankmodul das tun lassen, wofür es entwickelt wurde.

Joel Brown
quelle
2
Was ist, wenn die 'Eigenschaften, mit denen der Rang berechnet wird', willkürlich sind? ZB: Eine Reihe von Einkaufswageneinträgen, die basierend auf den willkürlichen Aktionen des Benutzers neu angeordnet werden.
Chitti
Wenn Sie sagen, der Rang sei willkürlich, was meinen Sie damit? Es muss einen Algorithmus geben, mit dem Sie den Rang berechnen können. Zum Beispiel: "basierend auf Warenkorbeinträgen" - basierend auf wie? In der Datenbank muss etwas gespeichert sein, das den Treiber für die Rangberechnung darstellt. Es kann eine Kombination aus mehreren Dingen sein, aber diese Dinge müssen irgendwie in der Kundentabelle oder in Tabellen gespeichert werden, die sich auf den Kunden beziehen. Wenn es in den Daten ist, können Sie eine Funktion erstellen, die es berechnet. Wenn Sie es berechnen können, können Sie es speichern und darüber indizieren.
Joel Brown
Angenommen, wir müssen die Reihenfolge der Artikel in einem Warenkorb beibehalten, und die Reihenfolge kann vom Benutzer über eine Webbenutzeroberfläche "willkürlich" geändert werden. Wie würden Sie eine solche Artikelliste in einer Datenbank speichern und wie würden Sie die Sortierreihenfolge beibehalten?
Chitti
Wenn ich Sie richtig verstehe, bedeutet das "willkürliche Ändern" der Reihenfolge der Artikel in einem Einkaufswagen, dass der Benutzer die Artikel in einer Liste nach oben und unten ziehen und dort ablegen kann, wo sie gewünscht werden. Ich denke, das scheint mir ein wenig erfunden zu sein. Warum sollten Benutzer das tun? Wenn sie es könnten, würden sie es oft tun? Ist die Verwendung einer einfachen Abfolge von Artikeln in einem Einkaufswagen wirklich so wichtig für die Leistung? Es scheint mir, dass eine fortlaufende Nummer von eins bis zur Anzahl der Artikel im Warenkorb + der FK bis zur Bestellung Ihnen den Index geben würde, den Sie benötigen. Aktualisieren Sie einfach die Elemente, wenn Sie herumgeschleift werden.
Joel Brown
3
Der Einkaufswagen ist nur ein Beispiel, das ich gegeben habe, um zu zeigen, dass es Fälle gibt, in denen der Rang willkürlich sein kann. Vielleicht war das kein gutes Beispiel. Die Netflix-DVD-Warteschlange kann ein besseres Beispiel sein. Stellen Sie sich aus Gründen der Argumentation eine Netflix-Warteschlange mit 100.000 Elementen vor, die vom Benutzer beliebig neu angeordnet werden kann und die er jede Minute ausführt. Wie würden Sie eine Datenbank entwerfen, um diese geordnete Liste von Filmen in dieser hypothetischen Anwendung zu speichern?
Chitti
1

Dies sind die Einschränkungen eines Nicht-RDBMS wie simpleDB. Die Funktionen, die Sie benötigen, können nicht auf der DB-Seite in simpleDB implementiert werden, sondern müssen von der Programmierseite / -anwendung aus implementiert werden.

Für ein RDBMS wie dieses SQL serversind die Funktionen, die Sie benötigen, für den Clustered-Index rudimentär.

  • Einfügen (x) - Datensatz x in die Tabelle einfügen> Einfaches Einfügen.
  • Löschen (x) - Datensatz x aus der Tabelle löschen> Einfaches Löschen.
  • Vorher (x, n) - Die 'n' Datensätze vor dem Datensatz x in der sortierten Liste zurückgeben. > Wählen Sie Top-n-Ergebnisse aus, bei denen x kleiner als der Wert ist, und sortieren Sie nach Klausel.

  • Nach (x, n) - Die 'n' Datensätze zurückgeben, die dem Datensatz x in der sortierten Liste folgen. > Wählen Sie Top-n-Ergebnisse aus, bei denen x größer als value ist, und sortieren Sie nach Klausel.

  • First (n) - Gibt die ersten n Datensätze aus der sortierten Liste zurück. > Wählen Sie die besten n Ergebnisse aus.

  • Last (n) - Gibt die letzten n Datensätze aus der sortierten Liste zurück. > Top n Ergebnisse nach Reihenfolge auswählen von absteigend.

  • Compare (x, y) - Wenn zwei Datensätze x und y aus der Tabelle gegeben sind, finde, ob x> y ist. > TSQL IF-Anweisung.
StanleyJohns
quelle
SimpleDB bietet automatische Indizes, Sortierung und eine grundlegende Abfragesprache . Mein Problem bleibt bestehen, auch wenn ich mich für ein RDBMS entscheide. Das Problem ist, dass sich die Rangfolge der Daten in meiner Datenbank willkürlich ändert und sie nicht als einzelne Eigenschaft erfasst werden können (es sei denn, ich verwende eine benutzerdefinierte Rangfolge-Spalte), die indiziert werden kann.
Chitti
0

Folgendes habe ich verwendet, um meine Postgres-Tabelle nach jeder Einfügung neu zu ordnen:

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();

Für meinen Anwendungsfall ist die Leistung kein Problem, aber die Gewissheit, dass sie niemals kaputt geht oder sich merkwürdig verhält, ist wichtig.

Kennzeichen
quelle