Kapitel 4. B-Bäume implementieren

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Im vorigen Kapitel haben wir über die allgemeinen Prinzipien der Zusammensetzung von Binärformaten gesprochen und gelernt, wie man Zellen erstellt, Hierarchien aufbaut und sie mit Hilfe von Zeigern mit Seiten verbindet. Diese Konzepte gelten sowohl für die In-Place-Update- als auch für die Append-Only-Speicherstrukturen. In diesem Kapitel besprechen wir einige Konzepte, die speziell für B-Trees gelten.

Die Abschnitte in diesem Kapitel sind in drei logische Gruppen unterteilt. Zunächst geht es um die Organisation: wie man Beziehungen zwischen Schlüsseln und Zeigern herstellt und wie man Kopfzeilen und Links zwischen Seiten implementiert.

Als Nächstes besprechen wir die Prozesse, die während des Abstiegs von der Wurzel zum Blatt ablaufen, nämlich wie man eine binäre Suche durchführt und wie man Breadcrumbs sammelt und den Überblick über die Elternknoten behält, falls wir später Knoten aufteilen oder zusammenführen müssen.

Schließlich werden Optimierungstechniken (Rebalancing, Right-Only Appends und Bulk Loading), Wartungsprozesse und Speicherbereinigung besprochen.

Binäre Suche

Wir haben auf bereits den B-Tree Lookup Algorithmus besprochen (siehe "B-Tree Lookup Algorithmus") und erwähnt, dass wir einen gesuchten Schlüssel innerhalb des Knotens mithilfe des binären Suchalgorithmus finden. Die binäre Suche funktioniert nur bei sortierten Daten. Wenn die Schlüssel nicht geordnet sind, können sie nicht binär durchsucht werden. Deshalb ist es wichtig, dass die Schlüssel geordnet sind und eine Sortierinvariante beibehalten wird.

Der binäre Suchalgorithmus erhält ein Array mit sortierten Elementen und einen gesuchten Schlüssel und gibt eine Zahl zurück. Wenn die zurückgegebene Zahl positiv ist, wissen wir, dass der gesuchte Schlüssel gefunden wurde und die Zahl gibt seine Position im Eingabefeld an. Ein negativer Rückgabewert zeigt an, dass der gesuchte Schlüssel nicht im Eingabefeld vorhanden ist und gibt uns einen Einfügepunkt an.

Der Einfügepunkt ist der Index des ersten Elements, das größer ist als der angegebene Schlüssel. Ein absoluter Wert dieser Zahl ist der Index, an dem der gesuchte Schlüssel eingefügt werden kann, um die Reihenfolge zu wahren. Das Einfügen kann durch das Verschieben von Elementen um eine Position, ausgehend von einem Einfügepunkt, erfolgen, um Platz für das eingefügte Element zu schaffen [SEDGEWICK11].

Die meisten Suchen auf höheren Ebenen führen nicht zu exakten Übereinstimmungen, und wir sind an der Suchrichtung interessiert. In diesem Fall müssen wir den ersten Wert finden, der größer ist als der gesuchte und dem entsprechenden Child-Link in den zugehörigen Teilbaum folgen.

Binäre Suche mit Indirektionszeigern

Die Zellen in der B-Baum-Seite werden in der Einfügereihenfolge gespeichert, und nur die Zellenversätze bewahren die logische Elementreihenfolge. Um eine binäre Suche durch die Seitenzellen durchzuführen, wählen wir den mittleren Zelloffset aus, folgen seinem Zeiger, um die Zelle zu finden, vergleichen den Schlüssel dieser Zelle mit dem gesuchten Schlüssel, um zu entscheiden, ob die Suche links oder rechts fortgesetzt werden soll, und setzen diesen Prozess rekursiv fort, bis das gesuchte Element oder der Einfügepunkt gefunden ist, wie in Abbildung 4-7 dargestellt.

Weitergabe von Splits und Merges

Wie wir in den vorherigen Kapiteln besprochen haben, können sich B-Tree-Splits und Merges auf höhere Ebenen ausbreiten. Dazu müssen wir in der Lage sein, eine Kette vom spaltenden Blatt oder einem Paar von zusammenführenden Blättern zurück zum Wurzelknoten zu durchlaufen.

B-Baum-Knoten können Zeiger auf übergeordnete Knoten enthalten. Da Seiten aus niedrigeren Ebenen immer ausgelagert werden, wenn sie von einer höheren Ebene aus referenziert werden, ist es nicht einmal notwendig, diese Informationen auf der Festplatte zu speichern.

Genau wie Geschwisterzeiger (siehe "Geschwisterverknüpfungen") müssen auch Elternzeiger immer dann aktualisiert werden, wenn sich der Elternknoten ändert. Dies geschieht in allen Fällen, in denen der Trennungsschlüssel mit der Seitenkennung von einem Knoten auf einen anderen übertragen wird: bei der Aufteilung, Zusammenlegung oder Neuordnung des übergeordneten Knotens.

Einige Implementierungen (z. B. WiredTiger) verwenden Parent-Pointer für das Durchlaufen von Blättern, um Deadlocks zu vermeiden, die bei der Verwendung von Sibling-Pointern auftreten können (siehe [MILLER78], [LEHMAN81]). Anstatt Geschwisterzeiger zum Durchlaufen von Blattknoten zu verwenden, setzt der Algorithmus Elternzeiger ein, wie wir in Abbildung 4-1 gesehen haben.

Um ein Geschwisterkind anzusprechen und zu finden, können wir einem Zeiger vom übergeordneten Knoten folgen und rekursiv bis zur unteren Ebene hinuntersteigen. Wenn wir das Ende des übergeordneten Knotens erreichen, nachdem wir alle Geschwister des übergeordneten Knotens durchlaufen haben, wird die Suche rekursiv nach oben fortgesetzt, bis wir schließlich die Wurzel erreichen und wieder auf die Blattebene hinuntergehen.

Rebalancing

Einige B-Tree-Implementierungen versuchen, Split- und Merge-Operationen zu verschieben, um ihre Kosten zu amortisieren, indem sie Elemente innerhalb der Ebene neu ausbalancieren oder Elemente so lange wie möglich von stärker besetzten Knoten auf weniger besetzte verschieben, bevor sie schließlich einen Split oder Merge durchführen. Dies trägt dazu bei, die Belegung der Knoten zu verbessern und kann die Anzahl der Ebenen innerhalb des Baums verringern, wobei die Kosten für die Neugewichtung möglicherweise höher sind .

Der Lastausgleich kann bei Einfüge- und Löschvorgängen durchgeführt werden [GRAEFE11]. Um die Platzausnutzung zu verbessern, können wir, anstatt den Knoten bei Überlauf zu teilen, einige Elemente auf einen der Geschwisterknoten übertragen und so Platz für die Einfügung schaffen. Ähnlich verhält es sich beim Löschen: Anstatt die Geschwisterknoten zusammenzulegen, können wir einige Elemente aus den Nachbarknoten verschieben, um sicherzustellen, dass der Knoten mindestens halb voll ist.

B*-Bäume verteilen die Daten so lange zwischen den benachbarten Knoten, bis beide Geschwister voll sind [KNUTH98]. Anstatt einen einzelnen Knoten in zwei halbleere aufzuteilen, teilt der Algorithmus dann zwei Knoten in drei Knoten auf, von denen jeder zu zwei Dritteln gefüllt ist. SQLite verwendet diese Variante in der Implementierung. Dieser Ansatz verbessert die durchschnittliche Auslastung durch die Verschiebung von Splits, erfordert aber zusätzliche Verfolgungs- und Ausgleichslogik. Eine höhere Auslastung bedeutet auch eine effizientere Suche, weil die Höhe des Baums kleiner ist und weniger Seiten auf dem Weg zum gesuchten Blatt durchlaufen werden müssen.

Abbildung 4-9 zeigt die Verteilung von Elementen zwischen den benachbarten Knoten, wobei der linke Geschwisterknoten mehr Elemente enthält als der rechte. Die Elemente des stärker besetzten Knotens werden auf den weniger besetzten verschoben. Da der Ausgleich die Min/Max-Invariante der Geschwisterknoten verändert, müssen wir die Schlüssel und Zeiger am übergeordneten Knoten aktualisieren, um sie zu erhalten.

dbin 0409
Abbildung 4-9. B-Baum Ausgleich: Verteilen von Elementen zwischen dem stärker besetzten und dem weniger besetzten Knoten

Der Lastausgleich ist eine nützliche Technik, die in vielen Datenbankimplementierungen verwendet wird. SQLite implementiert zum Beispiel den Algorithmus"Balancing Siblings", der dem in diesem Abschnitt beschriebenen Verfahren sehr ähnlich ist. Das Balancing macht den Code zwar etwas komplexer, aber da die Anwendungsfälle isoliert sind, kann es zu einem späteren Zeitpunkt als Optimierung implementiert werden.

Nur Rechtsanhänge

Viele Datenbanksysteme verwenden automatisch inkrementierende, monoton steigende Werte als primäre Indexschlüssel. In diesem Fall bietet sich eine Optimierungsmöglichkeit, da alle Einfügungen gegen Ende des Index (im ganz rechten Blatt) stattfinden, so dass die meisten Splits auf dem ganz rechten Knoten auf jeder Ebene stattfinden. Da die Schlüssel monoton inkrementiert werden und das Verhältnis von Anhängen zu Aktualisierungen und Löschungen gering ist, sind auch die Nicht-Blatt-Seiten weniger fragmentiert als im Fall von zufällig angeordneten Schlüsseln.

PostgreSQL nennt diesen Fall einen Fastpath. Wenn der eingefügte Schlüssel unbedingt größer ist als der erste Schlüssel in der rechten Seite und die rechte Seite genug Platz für den neu eingefügten Eintrag hat, wird der neue Eintrag an der entsprechenden Stelle im rechten Blatt im Cache eingefügt und der gesamte Lesepfad kann übersprungen werden.

SQLite hat ein ähnliches Konzept und nennt es quickbalance. Wenn der Eintrag ganz rechts eingefügt wird und der Zielknoten voll ist (d.h. er wird beim Einfügen zum größten Eintrag im Baum), wird, anstatt den Knoten neu auszubalancieren oder aufzuteilen, der neue Knoten ganz rechts zugewiesen und sein Zeiger zum übergeordneten Knoten hinzugefügt (mehr zur Implementierung von Balancing in SQLite siehe "Rebalancing"). Auch wenn dadurch die neu erstellte Seite fast leer bleibt (statt halb leer wie bei einer Knotenaufteilung), ist es sehr wahrscheinlich, dass der Knoten bald gefüllt wird.

Bulk Loading

Wenn wir vorsortierte Daten haben und diese in großen Mengen laden wollen oder den Baum neu aufbauen müssen (z. B. für eine Defragmentierung), können wir die Idee mit den nur rechts stehenden Anhängen noch weiter ausbauen. Da die Daten, die für die Erstellung des Baums benötigt werden, bereits sortiert sind, müssen wir beim Bulk-Loading nur die Elemente an der äußersten rechten Stelle des Baums anhängen.

In diesem Fall können wir Splits und Merges ganz vermeiden und den Baum von unten nach oben zusammensetzen, indem wir ihn Ebene für Ebene ausschreiben oder Knoten höherer Ebenen ausschreiben, sobald wir genügend Zeiger auf bereits geschriebene Knoten niedrigerer Ebenen haben.

Ein Ansatz für die Implementierung des Bulk Loading besteht darin, vorsortierte Daten auf der Blattebene seitenweise zu schreiben (anstatt einzelne Elemente einzufügen). Nachdem die Blattseite geschrieben wurde, propagieren wir ihren ersten Schlüssel an die übergeordnete Seite und verwenden einen normalen Algorithmus zum Aufbau höherer B-Baum-Ebenen [RAMAKRISHNAN03]. Da die angehängten Schlüssel in der sortierten Reihenfolge angegeben werden, finden alle Splits in diesem Fall am ganz rechten Knoten statt.

Da B-Bäume immer von der untersten (Blatt-)Ebene aus aufgebaut werden, kann die komplette Blattebene ausgeschrieben werden, bevor die Knoten der höheren Ebene zusammengesetzt werden. So haben wir alle Kindzeiger zur Hand, wenn die höheren Ebenen aufgebaut werden. Die Hauptvorteile dieses Ansatzes sind, dass wir keine Splits oder Merges auf der Festplatte durchführen müssen und gleichzeitig nur einen minimalen Teil des Baums (d. h. alle Eltern des aktuell füllenden Blattknotens) für die Zeit der Konstruktion im Speicher behalten müssen.

Unveränderliche B-Bäume können auf die gleiche Weise erstellt werden, aber im Gegensatz zu veränderbaren B-Bäumen benötigen sie keinen Platz für spätere Änderungen, da alle Operationen an einem Baum endgültig sind. Alle Seiten können vollständig ausgefüllt werden, was die Auslastung und damit die Leistung verbessert.

Komprimierung

Die unkomprimierten Rohdaten können einen erheblichen Overhead verursachen, und viele Datenbanken bieten Möglichkeiten, sie zu komprimieren, um Platz zu sparen. Der offensichtliche Kompromiss liegt hier zwischen der Zugriffsgeschwindigkeit und der Komprimierungsrate: Größere Komprimierungsraten können die Datengröße verbessern, so dass du mehr Daten in einem einzigen Zugriff abrufen kannst, benötigen aber möglicherweise mehr RAM und CPU-Zyklen, um sie zu komprimieren und zu dekomprimieren.

Die Komprimierung kann auf verschiedenen Granularitätsebenen erfolgen. Auch wenn die Komprimierung ganzer Dateien bessere Komprimierungsraten erzielen kann, ist sie nur begrenzt anwendbar, da die gesamte Datei bei einer Aktualisierung neu komprimiert werden muss. Die Komprimierung einer gesamten Indexdatei ist unpraktisch und schwer effizient zu implementieren: Um eine bestimmte Seite anzusprechen, muss die gesamte Datei (oder der Abschnitt mit den Komprimierungsmetadaten) aufgerufen (um einen komprimierten Abschnitt zu finden), dekomprimiert und verfügbar gemacht werden.

Eine Alternative ist, die Daten seitenweise zu komprimieren. Das passt gut zu unserer Diskussion, denn die Algorithmen, die wir bisher besprochen haben, verwenden Seiten mit fester Größe. Seiten können unabhängig voneinander komprimiert und dekomprimiert werden, so dass du die Komprimierung mit dem Laden und Flushen von Seiten verbinden kannst. Allerdings kann eine komprimierte Seite in diesem Fall nur einen Bruchteil eines Plattenblocks einnehmen, und da Übertragungen in der Regel in Einheiten von Plattenblöcken erfolgen, kann es notwendig sein, zusätzliche Bytes einzulagern [RAY95]. In Abbildung 4-10 siehst du eine komprimierte Seite (a), die weniger Platz benötigt als ein Plattenblock. Wenn wir diese Seite laden, blenden wir auch zusätzliche Bytes ein, die zu der anderen Seite gehören. Bei Seiten, die sich über mehrere Plattenblöcke erstrecken, wie (b) im gleichen Bild, müssen wir einen zusätzlichen Block lesen.

dbin 0410
Abbildung 4-10. Komprimierung und Blockauffüllung

Ein anderer Ansatz besteht darin, nur die Daten zu komprimieren, entweder zeilenweise (Komprimierung ganzer Datensätze) oder spaltenweise (Komprimierung einzelner Spalten). In diesem Fall sind die Seitenverwaltung und die Komprimierung entkoppelt.

Die meisten Open-Source-Datenbanken, die beim Schreiben dieses Buches überprüft wurden, verfügen über anpassbare Komprimierungsmethoden, die auf verfügbaren Bibliotheken wie Snappy, zLib, lz4 und vielen anderen basieren.

Da Kompressionsalgorithmen je nach Datensatz und möglichen Zielen (z. B. Kompressionsverhältnis, Leistung oder Speicher-Overhead) unterschiedliche Ergebnisse liefern, werden wir in diesem Buch nicht auf Vergleiche und Implementierungsdetails eingehen. Es gibt viele Übersichten, die verschiedene Kompressionsalgorithmen für unterschiedliche Blockgrößen bewerten (z. B. Squash Compression Benchmark) und sich dabei in der Regel auf vier Kennzahlen konzentrieren: Speicher-Overhead, Kompressionsleistung, Dekompressionsleistung und Kompressionsverhältnis. Diese Kennzahlen sind wichtig, wenn du eine Kompressionsbibliothek auswählst.

Vakuum und Wartung

Bisher haben wir hauptsächlich über die benutzerseitigen Vorgänge in B-Trees gesprochen. Es gibt jedoch noch andere Prozesse, die parallel zu den Abfragen ablaufen und die Integrität der Speicherung aufrechterhalten, Speicherplatz zurückgewinnen, den Overhead reduzieren und die Seiten in Ordnung halten. Indem wir diese Vorgänge im Hintergrund ausführen, können wir Zeit sparen und vermeiden, dass wir bei Einfügungen, Aktualisierungen und Löschungen den Preis für Aufräumarbeiten zahlen müssen.

Das beschriebene Design von slotted pages (siehe "Slotted Pages") erfordert, dass die Seiten gewartet werden, um sie in gutem Zustand zu halten. Zum Beispiel können nachfolgende Splits und Merges in internen Knoten oder Inserts, Updates und Deletes auf der Blattebene dazu führen, dass eine Seite genug logischen Platz hat, aber nicht genug zusammenhängenden Platz, da sie fragmentiert ist. Abbildung 4-11 zeigt ein Beispiel für eine solche Situation: Die Seite verfügt noch über einen gewissen logischen Speicherplatz, der jedoch fragmentiert ist und zwischen den beiden gelöschten (Garbage-)Datensätzen und dem verbleibenden freien Speicherplatz zwischen den Header-/Zellzeigern und Zellen aufgeteilt ist.

dbin 0411
Abbildung 4-11. Ein Beispiel für eine fragmentierte Seite

B-Trees werden von der Wurzelebene aus navigiert. Datensätze, die erreicht werden können, indem man vom Wurzelknoten aus Zeigern nach unten folgt, sind live (adressierbar). Nicht adressierbare Datensätze werden als Müll bezeichnet: Diese Datensätze werden nirgendwo referenziert und können weder gelesen noch interpretiert werden, sodass ihr Inhalt so gut wie nichtig ist.

Du kannst diesen Unterschied in Abbildung 4-11 sehen: Zellen, die noch Zeiger auf sie haben, sind adressierbar, im Gegensatz zu den entfernten oder überschriebenen Zellen. Aus Leistungsgründen wird das Auffüllen von Garbage Areas oft übersprungen, da diese Bereiche irgendwann sowieso von den neuen Daten überschrieben werden.

Durch Aktualisierungen und Löschungen verursachte Fragmentierung

Betrachten wir unter welchen Umständen Seiten in einen Zustand geraten, in dem sie nicht adressierbare Daten haben und verdichtet werden müssen. Auf der Blattebene werden beim Löschen nur die Offsets der Zelle aus der Kopfzeile entfernt, die Zelle selbst bleibt jedoch intakt. Danach ist die Zelle nicht mehr adressierbar, ihr Inhalt erscheint nicht in den Abfrageergebnissen, und es ist nicht nötig, sie zu löschen oder benachbarte Zellen zu verschieben.

Wenn die Seite geteilt wird, werden nur die Offsets gekürzt. Da der Rest der Seite nicht adressierbar ist, sind die Zellen, deren Offsets gekürzt wurden, nicht erreichbar, so dass sie überschrieben werden, wenn die neuen Daten ankommen, oder zu Müll werden, wenn der Vakuumprozess einsetzt.

Hinweis

Einige Datenbanken verlassen sich auf die Speicherbereinigung und lassen entfernte und aktualisierte Zellen für die Multiversions-Gleichzeitigkeitskontrolle stehen (siehe "Multiversions-Gleichzeitigkeitskontrolle"). Die Zellen bleiben für die gleichzeitig ausgeführten Transaktionen zugänglich, bis die Aktualisierung abgeschlossen ist, und können eingesammelt werden, sobald kein anderer Thread auf sie zugreift. Einige Datenbanken unterhalten Strukturen, die Ghost Records verfolgen, die eingesammelt werden, sobald alle Transaktionen, die sie möglicherweise gesehen haben, abgeschlossen sind [WEIKUM01].

Da beim Löschen nur die Offsets der Zellen verworfen werden und die verbleibenden Zellen nicht verschoben oder die Zielzellen physisch entfernt werden, um den freigewordenen Platz zu belegen, können die freigegebenen Bytes über die Seite verstreut sein. In diesem Fall sagen wir, dass die Seite fragmentiert ist und defragmentiert werden muss .

Für einen Schreibvorgang benötigen wir oft einen zusammenhängenden Block freier Bytes, in den die Zelle passt. Um die freigewordenen Fragmente wieder zusammenzufügen und diese Situation zu beheben, müssen wir die Seite neu schreiben.

Bei Einfügevorgängen bleiben die Tupel in ihrer Einfügereihenfolge. Das hat zwar keine so großen Auswirkungen, aber natürlich sortierte Tupel können beim Cache-Prefetch bei sequentiellen Lesevorgängen helfen.

Aktualisierungen werden hauptsächlich auf der Blattebene vorgenommen: Interne Seitenschlüssel werden für die geführte Navigation verwendet und definieren nur die Grenzen der Teilbäume. Außerdem werden Aktualisierungen pro Schlüssel durchgeführt und führen im Allgemeinen nicht zu strukturellen Änderungen im Baum, abgesehen von der Erstellung von Überlaufseiten. Auf der Blattebene hingegen ändern Aktualisierungsvorgänge die Reihenfolge der Zellen nicht und versuchen, das Neuschreiben von Seiten zu vermeiden. Das bedeutet, dass mehrere Versionen der Zelle, von denen nur eine adressierbar ist, gespeichert werden können.

Defragmentierung der Seite

Der Prozess, der sich um die Rückgewinnung von Speicherplatz und das Neuschreiben von Seiten kümmert, wird Verdichtung, Vakuum oder einfach Wartung genannt. Das Neuschreiben von Seiten kann synchron beim Schreiben erfolgen, wenn die Seite nicht genügend freien Platz hat (um zu vermeiden, dass unnötige Überlaufseiten erstellt werden), aber die Verdichtung wird meist als ein eigenständiger, asynchroner Prozess bezeichnet, bei dem Seiten durchlaufen, Speicherbereinigung durchgeführt und ihr Inhalt neu geschrieben wird.

Bei diesem Vorgang wird der von toten Zellen belegte Platz zurückgewonnen und die Zellen werden in ihrer logischen Reihenfolge neu geschrieben. Wenn Seiten neu geschrieben werden, können sie auch an neue Positionen in der Datei verschoben werden. Ungenutzte Seiten im Speicher werden verfügbar und gehen zurück in den Seiten-Cache. Die IDs der neu verfügbaren Festplattenseiten werden der Liste der freien Seiten hinzugefügt (manchmal auch Freelist genannt3). Diese Informationen müssen aufbewahrt werden, um Knotenabstürze und Neustarts zu überstehen und um sicherzustellen, dass kein freier Speicherplatz verloren geht oder verloren geht.

Zusammenfassung

In diesem Kapitel haben wir die Konzepte besprochen, die für B-Tree-Implementierungen auf der Festplatte spezifisch sind, wie z. B.:

Seitenüberschrift

Welche Informationen werden dort normalerweise gespeichert?

Zeiger ganz rechts

Diese sind nicht mit Trennungsschlüsseln gepaart, und wie man sie behandelt.

Hohe Tasten

Bestimme den maximal zulässigen Schlüssel, der in dem Knoten gespeichert werden kann.

Überlaufseiten

Ermöglicht es dir, Datensätze mit Übergröße und variabler Größe auf Seiten mit fester Größe zu speichern.

Danach haben wir uns einige Details zum Root-to-Leaf-Traversal angesehen:

  • Wie man eine binäre Suche mit Umleitungszeigern durchführt

  • Wie man den Überblick über Baumhierarchien mit Hilfe von Elternzeigern oder Breadcrumbs behält

Zum Schluss haben wir noch einige Optimierungs- und Wartungstechniken besprochen:

Rebalancing

Verschiebt Elemente zwischen benachbarten Knoten, um die Anzahl der Splits und Merges zu reduzieren.

Nur rechts anhängen

Hängt die neue Zelle ganz rechts an, anstatt sie zu teilen, in der Annahme, dass sie sich schnell füllen wird.

Schüttgutverladung

Eine Technik zur effizienten Erstellung von B-Trees aus sortierten Daten.

Speicherbereinigung

Ein Prozess, der die Seiten umschreibt, die Zellen in die richtige Reihenfolge bringt und den Platz von nicht adressierbaren Zellen zurückgewinnt.

Diese Konzepte sollen die Lücke zwischen dem grundlegenden B-Tree-Algorithmus und einer realen Implementierung schließen und dir helfen, besser zu verstehen, wie B-Tree-basierte Speichersysteme funktionieren.

1 Du findest diesen Algorithmus in der Funktion balance_deeper in der Projektdatenbank.

2 Du kannst mehr darüber in der Projektdatenbank lesen: https://databass.dev/links/21.

3 SQLite verwaltet zum Beispiel eine Liste von Seiten, die nicht von der Datenbank verwendet werden, wobei die Stammseiten in einer verknüpften Liste gehalten werden und die Adressen der freigegebenen Seiten enthalten.

Get Datenbank Interna now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.