# Motivation und Grundlagen ## Aufgaben und Komponenten eines DBMS Prinzipien: Die neun Codd’schen Regeln 1. Integration: einheitliche, nichtredundante Datenverwaltung 2. Operationen: Speichern, Suchen, Ändern 3. Katalog: Zugriffe auf Datenbankbeschreibungen im Data Dictionary 4. Benutzersichten 5. Integritätssicherung: Korrektheit des Datenbankinhalts 6. Datenschutz: Ausschluss unauthorisierter Zugriffe 7. Transaktionen: mehrere DB-Operationen als Funktionseinheit 8. Synchronisation: parallele Transaktionen koordinieren 9. Datensicherung: Wiederherstellung von Daten nach Systemfehlern Betrachtete Fragestellung ![](Assets/DBimpl-ebenen.png) **Zentrale Komponenten** - **Anfrageverarbeitung** : Planung, Optimierung und Ausführung deklarativer Anfragen - **Transaktionsverwaltung** : Koordination und Synchronisation von Transaktionen, Durchführung von Änderungen, Sicherung der ACID-Eigenschaften - **Speichersystem** : Organisation der Daten im Hauptspeicher und auf dem Externspeicher für effizienten Zugriff und Persistenz ## Relationale vs. nicht-relationale DBMS **Relationale DBMS** - Basis: **Relationenmodell** = Daten in Tabellen strukturiert - Beziehungen über Werte (= Fremdschlüssel), Integritätsbedingungen - **SQL** als standardisierte Anfragesprache - kommerziell erfolgreichstes Datenmodell: Oracle, IBM DB2, MS SQL Server, SAP HANA, ... | WEINE | WeinID | Name | Farbe | Jahrgang | Weingut | | ----- | ----------------- | ---- | ----- | ----------- | ------- | | 1042 | La Rose Grand Cru | Rot | 1998 | Château ... | | 2168 | Creek Shiraz | Rot | 2003 | Creek | | 3456 | Zinfandel | Rot | 2004 | Helena | | 2171 | Pinot Noir | Rot | 2001 | Creek | | 3478 | Pinot Noir | Rot | 1999 | Helena | | 4711 | Riesling Reserve | Weiß | 1999 | Müller | | 4961 | Chardonnay | Weiß | 2002 | Bighorn | **Kritik an RDBMS / SQL** - nicht skalierbar - Normalisierung von Relationen, viele Integritätsbedingungen zu prüfen - kann man in RDBMS auch vermeiden! - starre Tabellen nicht flexibel genug - schwach typisierte Tabellen (Tupel weichen in den tatsächlich genutzten Attributen ab) - viele Nullwerte wenn alle potentiellen Attribute definiert - alternativ Aufspaltung auf viele Tabellen - Schema-Evolution mit **alter table** unflexibel - tatsächlich in vielen Anwendungen ein Problem - Integration von spezifischen Operationen (Graphtraversierung, Datenanalyse-Primitive) mit Stored Procedures zwar möglich führt aber oft zu schwer interpretierbarem Code **NoSQL-Systeme** - Datenmodelle - KV-Stores - Wide Column Stores - Dokumenten-orientierte Datenhaltung - Graph-Speicher - ... - Anfragesprache -> unterschiedliche Ansätze: - einfache funktionale API - Programmiermodell für parallele Funktionen - angelehnt an SQL-Syntax - ... - Beispiele - dokumentenorientierte Datenbanksysteme: MongoDB - semistrukturierte Dokumente in JSON- bzw. BSON-Format - Anfragen: CRUD erweitert um dokumentspezifische Suche - Graph-Datenbanksysteme: Neo4j - Property Graphen als Datenmodell: Knoten und Kanten mit Eigenschaften - Anfragesprache Cypher - Muster der Form "Knoten -> Kante -> Knoten ..." ## OLTP, OLAP und HTAP ### OLTP vs OLAP | | Online Transactional Processing (OLTP) | Online Analytical Processing (OLAP) | | -------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ | | | -> Klassische operative Informationssysteme | -> Data Warehouse | | | Erfassung und Verwaltung von Daten | Analyse im Mittelpunkt = entscheidungsunterstützende Systeme | | | Verarbeitung unter Verantwortung der jeweiligen Abteilung | Langandauernde Lesetransaktionen auf vielen Datensätzen | | | Transaktionale Verarbeitung: kurze Lese-/ Schreibzugriffe auf wenigen Datensätzen | Integration, Konsolidierung und Aggregation der Daten | | | ACID-Eigenschaften | | | | | | **Anfragen** | | | Fokus | Lesen, Schreiben, Modifizieren, Löschen | Lesen, periodisches Hinzufügen | | Transaktionsdauer und -typ | kurze Lese- / Schreibtransaktionen | langandauernde Lesetransaktionen | | Anfragestruktur | einfach strukturiert | komplex | | Datenvolumen einer Anfrage | wenige Datensätze | viele Datensätze | | Datenmodell | anfrageflexibel | analysebezogen | | Antwortzeit | msecs ...secs | secs ...min | | | | | **Daten** | | | Datenquellen | meist eine | mehrere | | Eigenschaften | nicht abgeleitet, zeitaktuell, autonom, dynamisch | abgeleitet / konsolidiert, historisiert, integriert, stabil | | Datenvolumen | MByte ...GByte | GByte ...TByte ...PByte | | Zugriffe | Einzeltupelzugriff | Tabellenzugriff (spaltenweise) | **OLTP: Beispiel** ```sql BEGIN ; SELECT KundenNr INTO KNr FROM Kunden WHERE email = '...'; INSERT INTO BESTELLUNG VALUES (KNr, BestNr, 1); UPDATE Artikel SET Bestand = Bestand-1 WHERE ArtNr = BestNr; COMMIT TRANSACTION ; ``` **OLAP: Beispiel** ```sql SELECT DISTINCT ROW Zeit.Dimension AS Jahr, Produkt.Dimension AS Artikel, AVG(Fact.Umsatz) AS Umsatzdurchschnitt, Ort.Dimension AS Verkaufsgebiet FROM (Produktgruppe INNER JOIN Produkt ON Produktgruppe. [Gruppen-Nr] = Produkt.[Gruppen-ID]) INNER JOIN ((((Produkt INNER JOIN [Fact.Umsatz] ON Produkt.[Artikel-Nr] = [Fact.Umsatz].[Artikel-Nr]) INNER JOIN Order ON [Fact.Umsatz].[Bestell-Nr]= Order.[Order-ID]) INNER JOIN Zeit.Dimension ON Orders.[Order-ID] = Zeit.Dimension.[Order-ID]) INNER JOIN Ort.Dimension ON Order.[Order-ID] = Ort.Dimension.[Order-ID]) ON Produktgruppe.[Gruppen-Nr] = Produkt.[Gruppen-ID] GROUP BY Produkt.Dimension.Gruppenname, Ort.Dimension.Bundesland, Zeit.Dimension.Jahr; ``` ### HTAP - HTAP = Hybrid Transactional and Analytics Processing - Ziel: schnellere Geschäftsentscheidungen durch "Echtzeit"-Verarbeitung - OLAP und OLTP auf der gleichen Datenbank: naheliegend aber große technische Herausforderung - sehr unterschiedliche Workloads (Anfrage- und Lastprofile) - Transaktionsverwaltung: gegenseitige Beeinflussung von Änderungs- und Leseoperationen reduzieren - unterschiedliche Datenorganisation (physisch, logisch) - Herausforderungen - Analytical (OLAP) und Transactional processing (OLTP) - verschiedene Zugriffscharakterisiken - verschiedene Performance-Ziele (Latenz vs. Durchsatz) - => Unterschiedliche Optimierungen notwendig ## Disk- vs. Main-Memory-Systeme** **Traditionelle Annahmen** - Daten sollen dauerhauft aufbewahrt werden - Datenbank >> Hauptspeicher - Disk >> Hauptspeicher - Hauptspeicher = flüchtiger (volatiler) Speicher - Disk-IO dominiert Kosten **Speicherhierarchie** ![](Assets/DBimpl-speicherhierarchie.png) **Eigenschaften von Speichermedien** | | Primär | Sekundär | Tertiär | | --------------- | -------- | --------- | ------------ | | Geschwindigkeit | schnell | langsam | sehr langsam | | Preis | teuer | preiswert | billig | | Stabilität | flüchtig | stabil | stabil | | Größe | klein | groß | sehr groß | | Granulate | fein | grob | grob | **Speichermedien** - **Primärspeicher** - Primärspeicher: Cache und Hauptspeicher - sehr schnell, Zugriff auf Daten fein granular: theoretisch jedes Byte adressierbar (Cachelines) - **Sekundärspeicher** - Sekundärspeicher oder Online-Speicher - meist Plattenspeicher, nicht-flüchtig - Granularität des Zugriffs gröber: Blöcke, oft 512 Bytes - Zugriffslücke: Faktor 10^5 langsamerer Zugriff - **Tertiärspeicher** - Zur langfristigen Datensicherung (Archivierung) oder kurzfristigen Protokollierung (Journale) - üblich: optische Platten, Magnetbänder - "Offline-Speicher" meist Wechselmedium - Nachteil: Zugriffslücke extrem groß **Transferraten HDD vs. SSD** ![](Assets/DBimpl-Transferrate.png) **Konsequenz für disk-basierte Systeme** - blockbasierter Zugriff mit typischen Blockgrößen ≥ 4 KB - speziell für Magnetplatten Optimierung auf sequentielle Zugriffe - Disklayout: Organisation der Daten auf der Disk = fortlaufende Folge von Blöcken - sequentielles Lesen und Schreiben - Zugriffslücke zwischen Hauptspeicher und Disk durch Caching verbergen (Lokalität von Zugriffen ausnutzen) **Main-Memory-Datenbanken** - klassische Annahmen nicht mehr zutreffend: - Systeme mit Hauptspeicher im TB-Bereich verfügbar - Datenbank kann komplett im Hauptspeicher gehalten werden (muss aber dennoch persistent sein) - **Main-Memory-** oder **Hauptspeicher-** Datenbanken: Ausnutzung der großen Hauptspeicher und Multicore-Architekturen - Beispiele: SAP HANA, Oracle TimesTen, SQL Server Hekaton, Hyper, MemSQL, ... - Besonderheiten: hauptspeicheroptimierte Datenstrukturen (Main-Memory-Scans), Persistenz trotz volatilem Speicher, Datenkompression, Nebenläufigkeitskontrolle ## Klassische 5-Schichtenarchitektur **Fünf-Schichtenarchitektur** - Architektur für klassische DBMS - basierend auf Idee von Senko 1973 - Weiterentwicklung von Härder 1987 - Umsetzung im Rahmen des IBM-Prototyps _System R_ - genauere Beschreibung der Transformationskomponenten - schrittweise Transformation von Anfragen/Änderungen bis hin zu Zugriffen auf Speichermedien - Definition der Schnittstellen zwischen Komponenten **5-Schichtenarchitektur: Funktionen** ![](Assets/DBimpl-5-schichten-funktionen.png) **5-Schichtenarchitektur: Objekte** ![](Assets/DBimpl-5-schichten-objekte.png) Erläuterungen - mengenorientierte Schnittstelle **MOS** : - deklarative Datenmanipulationssprache auf Tabellen und Sichten (etwa SQL) - durch Datensystem auf satzorientierte Schnittstelle **SOS** umgesetzt: - navigierender Zugriff auf interner Darstellung der Relationen - manipulierte Objekte: typisierte Datensätze und interne Relationen sowie logische Zugriffspfade (Indexe) - Aufgaben des Datensystems: Übersetzung und Optimierung von SQL-Anfragen - durch Zugriffssystem auf interne Satzschnittstelle **ISS** umgesetzt: - interne Tupel einheitlich verwalten, ohne Typisierung - Speicherstrukturen der Zugriffspfade (konkrete Operationen auf B+-Bäumen und Hashtabellen), Mehrbenutzerbetrieb mit Transaktionen - durch Speichersystem Datenstrukturen und Operationen der ISS auf interne Seiten eines virtuellen linearen Adressraums umsetzen - Manipulation des Adressraums durch Operationen der Systempufferschnittstelle **SPS** - Typische Objekte: interne Seiten, Seitenadressen - Typische Operationen: Freigeben und Bereitstellen von Seiten, Seitenwechselstrategien, Sperrverwaltung, Schreiben des Logs - durch Pufferverwaltung interne Seiten auf Blöcke der Dateischnittstelle **DS** abbilden - Umsetzung der DS-Operationen auf Geräteschnittstelle erfolgt durch BS ## Neue Entwicklungen Anforderungen aus neuen Anwendungen - Nicht-Standard-Datenmodelle (siehe NoSQL-Systeme) - flexibler Umgang mit Datenstrukturen (JSON, Schema on Read, ...) - beschränkte (Lookups) vs. erweiterte (z.B. Graphoperationen, Datenanalysen) Anfragefunktionalität - Skalierbarkeit zu Big Data (massiv parallele/verteilte Systeme) - dynamische Daten / Datenströme - ... **Entwicklungen im Hardware-Bereich** - Multicore- und Manycore-Prozessoren: 64+ Cores - Nutzung erfordert Parallelisierungstechniken und Nebenläufigkeitskontrolle - Memory Wall: Hauptspeicherzugriff als Flaschenhals - RAM-Zugriff 60 ns, L1-Cache: 4 CPU-Zyklen -> Cache-optimierte Strukturen - Datenbank-Accelerators - Hardware-unterstütztes Datenmanagement: FPGA, GPU als Coprozessoren, Highspeed-Netzwerk, SSDs als zusätzliche Cache-Ebene, ... - Persistenter Memory: nicht-volatiler Speicher - Instant Restart / Recovery von Main-Memory-Datenbanken **Zusammenfassung** - Datenmanagementfunktionalitäten in vielen Softwaresystemen erforderlich - nicht auf Implementierung kompletter DBMS beschränkt, sondern für nahezu alle datenintensiven Systeme: auch in Suchmaschinen, Datenanalyseanwendungen, eingebetteten Systemen, Visualisierungssystemen, Steuerungssystemen, Entwicklungsumgebungen, ... - gemeinsame Aufgaben / Komponenten: Datenorganisation und -verwaltung (Indexstrukturen), Transaktionsverwaltung / Nebenläufigkeitskontrolle / Recovery, Anfrageverarbeitung - betrifft Datenstrukturen und Algorithmen # Speicherstrukturen für Datenbanken ## Speicher- und Sicherungsmedien Speichermedien - verschiedene Zwecke: - Daten zur Verarbeitung bereitstellen - Daten langfristig speichern (und trotzdem schnell verfügbar halten) - Daten sehr langfristig und preiswert archivieren unter Inkaufnahme etwas längerer Zugriffszeiten - Speicherhierarchie: 1. Extrem schneller Prozessor mit Registern 2. Sehr schneller Cache-Speicher 3. Schneller Hauptspeicher 4. Langsamer Sekundärspeicher mit wahlfreiem Zugriff 5. Sehr langsamer Nearline-Tertiärspeicher bei dem die Speichermedien automatisch bereitgestellt werden 6. Extrem langsamer Offline-Tertiärspeicher, bei dem die Speichermedien per Hand bereitgestellt werden Zugriffslücke in Zahlen - Zugriffslücke: Unterschiede in den Zugriffsgeschwindigkeiten auf den verschiedenen Speicherebenen | Speicherart | Zugriffszeit | CPU cycles | typische Kapazität | | ------------------------ | ------------ | ---------- | -------------------------- | | CacheSpeicher | 6 ns | 12 | 256 KB (L2) bis 32 MB (L3) | | Hauptspeicher | 60 ns | 120 | 1 GB bis 1.5 TB | | Zugriffslücke $10^5$ | | | | Magnetplattenspeicher | 8-12 ms | 16*10^6 | 160 GB bis 4 TB | | Platten-Farm oder -Array | 12 ms | 24*10^6 | im TB- bis PB-Bereich | Typische Merkmale von Sekundärspeicher | Merkmal | Kapazität | Latenz | Bandbreite | | --------------- | --------- | ------- | -------------------- | | 1983 | 30 MB | 48.3 ms | 0.6 MB/s | | 1994 | 4.3 GB | 12.7 ms | 9 MB/s | | 2003 | 73.4 GB | 5.7 ms | 86 MB/s | | 2009 | 2 TB | 5.1 ms | 95 MB/s | | 2019 SSD (NVMe) | 2 TB | ?? | seq.read 3.500 MB/s | | | | ?? | seq.write 1.600 MB/s | Solid State Disk (SSD) - basierend auf EEPROMs in NAND- oder NOR-Technologie - Arrays (=Flash-Block mit ca. 128 KB) von Speicherzellen, entweder ein Bit (SLC) oder 2-4 Bit (MLC) - MLC sind langsamer und haben verkürzte Lebensdauer - initial ist jedes Bit auf 1 gesetzt, durch Reprogrammieren auf 0 - Löschen zurück auf 1 nur für ganzen Block - Konsequenz: langsames Löschen (Lesen = 25 μs, Löschen = 2 ms), begrenzte Lebensdauer (ca. 100.000 Lösch-Schreib-Zyklen) - Schnittstelle: SATA oder PCIe (NVMe) ![](Assets/DBimpl-SSD.png) SSDs in DBMS - klassische, auf sequenzielles Lesen ausgerichtete, Strategien von DBMS nutzen die Stärken von Flash-Speicher nicht aus - kleinere Blockgrößen lassen sich effizient adressieren, sollten aber ein Vielfaches der Flash-Seiten sein - wahlfreie Lesezugriffe sind effizienter als auf Magnetplatten, sollten aber auf Größen von ca. 4 bis 16 MB begrenzt werden - konkurrierende IO-Zugriffe sind bis zu einem gewissen Maße ohne negativen Performanzeinfluss durchführbar **Speicherarrays: RAID** - Kopplung billiger Standardplatten unter einem speziellen Controller zu einem einzigen logischen Laufwerk - Verteilung der Daten auf die verschiedenen physischen Festplatten übernimmt Controller - zwei gegensätzliche Ziele: - Erhöhung der Fehlertoleranz (Ausfallsicherheit, Zuverlässigkeit) durch Redundanz - Effizienzsteigerung durch Parallelität des Zugriffs Erhöhung der Fehlertoleranz - Nutzung zusätzlicher Platten zur Speicherung von Duplikaten (Spiegeln) der eigentlichen Daten => bei Fehler: Umschalten auf Spiegelplatte - bestimmte RAID-Levels (1, 0+1) erlauben eine solche Spiegelung - Alternative: Kontrollinformationen wie Paritätsbits nicht im selben Sektor wie die Originaldaten, sondern auf einer anderen Platte speichern - RAID-Levels 2 bis 6 stellen durch Paritätsbits oder Error Correcting Codes (ECC) fehlerhafte Daten wieder her - ein Paritätsbit kann einen Plattenfehler entdecken und bei Kenntnis der fehlerhaften Platte korrigieren Erhöhung der Effizienz - Datenbank auf mehrere Platten verteilen, die parallel angesteuert werden können => Zugriffszeit auf große Datenmengen verringert sich fast linear mit der Anzahl der verfügbaren Platten - Verteilung: bit-, byte- oder blockweise - höhere RAID-Levels (ab Level 3) verbinden Fehlerkorrektur und block- oder bitweises Verteilen von Daten - Unterschiede: - schnellerer Zugriff auf bestimmte Daten - höherer Durchsatz für viele parallel anstehende Transaktionen durch eine Lastbalancierung des Gesamtsystems RAID-Levels ![](Assets/DBimpl-Raid-level-1.png) ![](Assets/DBimpl-Raid-level-2.png) | Level | Striping blockweise | Striping bitweise | Kopie | Parität | Parität dedizierte Platte | Parität verteilt | Erkennen mehrerer Fehler | | ----- | ------------------- | ----------------- | ----- | ------- | ------------------------- | ---------------- | ------------------------ | | 0 | √ | | 1 | | | √ | | 0+1 | √ | | √ | | 2 | | √ | | √ | | 3 | | √ | | √ | √ | | 4 | √ | | | √ | √ | | 5 | √ | | | √ | | √ | | 6 | √ | | | √ | | | √ | **Sicherungsmedien: Tertiärspeicher** - weniger oft benutzte Teile der Datenbank, die eventuell sehr großen Umfang haben (Text, Multimedia) "billiger" speichern als auf Magnetplatten - aktuell benutzte Datenbestände zusätzlich sichern (archivieren) - Tertiärspeicher: Medium austauschbar - offline: Medien manuell wechseln (optische Platten, Bänder) - nearline: Medien automatisch wechseln (_Jukeboxes_, _Bandroboter_) Langzeitarchivierung - Lebensdauer, Teilaspekte: - physische Haltbarkeit des Mediums garantiert die Unversehrtheit der Daten: - 10 Jahre für Magnetbänder, - 30 Jahre für optische Platten, - Papier??? - Vorhandensein von Geräten und Treibern garantiert die Lesbarkeit von Daten: - Geräte für Lochkarten oder 8-Zoll-Disketten? - zur Verfügung stehende Metadaten garantieren die Interpretierbarkeit von Daten - Vorhandensein von Programmen, die auf den Daten arbeiten können, garantieren die Wiederverwendbarkeit von Daten ## Struktur des Hintergrundspeichers Verwaltung des Hintergrundspeichers - Abstraktion von Speicherungs- oder Sicherungsmediums - Modell: Folge von Blöcken ![](Assets/DBimpl-5-schichten-funktionen.png) - Alternativen: - jede Relation oder jeder Zugriffspfad in genau einer Betriebssystem-Datei - ein oder mehrere BS-Dateien, DBS verwaltet Relationen und Zugriffspfade selbst innerhalb dieser Dateien - DBS steuert selbst Magnetplatte an und arbeitet mit den Blöcken in ihrer Ursprungsform ( _raw device_ ) - Warum nicht immer BS-Dateiverwaltung? - Betriebssystemunabhängigkeit - In 32-Bit-Betriebssystemen: Dateigröße 4 GB maximal - BS-Dateien auf maximal einem Medium - betriebssystemseitige Pufferverwaltung von Blöcken des Sekundärspeichers im Hauptspeicher genügt nicht den Anforderungen des Datenbanksystems Blöcke und Seiten - Zuordnung der physischen Blöcke zu Seiten - meist mit festen Faktoren: 1, 2, 4 oder 8 Blöcke einer Spur auf eine Seite - hier: "ein Block — eine Seite" - höhere Schichten des DBS adressieren über Seitennummer Dienste des Dateisystems - Allokation oder Deallokation von Speicherplatz - Holen oder Speichern von Seiteninhalten - Allokation möglichst so, dass logisch aufeinanderfolgende Datenbereiche (etwa einer Relation) auch möglichst in aufeinanderfolgenden Blöcken der Platte gespeichert werden - Nach vielen Update-Operationen: Reorganisationsmethoden - Freispeicherverwaltung: doppelt verkettete Liste von Seiten Abbildung der Datenstrukturen - Abbildung der konzeptuellen Ebene auf interne Datenstrukturen - Unterstützung durch Metadaten (im Data Dictionary, etwa das interne Schema) | Konz. Ebene | Interne Ebene | Dateisystem/Platte | | ---------------- | --------------- | ------------------ | | Relationen -> | Log. Dateien -> | Phys. Dateien | | Tupel -> | Datensätze -> | Seiten/Blöcke | | Attributwerte -> | Felder -> | Bytes | - Beispiel: jede Relation in je einer logischen Datei, diese insgesamt in einer einzigen physischen Datei ## Seiten, Sätze und Adressierung ### Seite - Block: - kleinste adressierbare Einheit auf Externspeicher - Zuordnung zu Seiten im Hauptspeicher - Aufbau von Seiten - Header - Informationen über Vorgänger- und Nachfolger-Seite - eventuell auch Nummer der Seite selbst - Informationen über Typ der Sätze - freier Platz - Datensätze - unbelegte Bytes Seitenorganisation - Organisation der Seiten: doppelt verkettete Liste - freie Seiten in Freispeicherverwaltung ![](Assets/DBimpl-seitenorganisation.png) Seite: Adressierung der Datensätze - adressierbare Einheiten - Zylinder - Spuren - Sektoren - Blöcke oder Seiten - Datensätze in Blöcken oder Seiten - Datenfelder in Datensätzen - Beispiel: Adresse eines Satzes durch Seitennummer und Offset (relative Adresse in Bytes vom Seitenanfang) Seitenzugriff als Flaschenhals - Maß für die Geschwindigkeit von Datenbankoperationen: Anzahl der Seitenzugriffe auf dem Sekundärspeicher (wegen Zugriffslücke) - Faustregel: Geschwindigkeit des Zugriffs ⇐ Qualität des Zugriffspfades ⇐ Anzahl der benötigten Seitenzugriffe - Hauptspeicheroperationen nicht beliebig vernachlässigbar Einpassen von Datensätzen auf Blöcke - Datensätze (eventuell variabler Länge) in die aus einer fest vorgegebenen Anzahl von Bytes bestehenden Blöcke einpassen: Blocken - Blocken abhängig von variabler oder fester Feldlänge der Datenfelder - Datensätze mit variabler Satzlänge: höherer Verwaltungsaufwand beim Lesen und Schreiben, Satzlänge immer wieder neu ermitteln - Datensätze mit fester Satzlänge: höherer Speicheraufwand Verschiedene Satztypen ![](Assets/DBimpl-satztypen.png) Sätze fester Länge - SQL: Datentypen fester und variabler Länge - _char(n)_ Zeichenkette der festen Länge _n_ - _varchar(n)_ Zeichenkette variabler Länge mit der Maximallänge _n_ - Aufbau der Datensätze, falls alle Datenfelder feste Länge: 1. Verwaltungsblock mit Typ eines Satzes (wenn unterschiedliche Satztypen auf einer Seite möglich) und Löschbit 2. Freiraum zur Justierung des Offset 3. Nutzdaten des Datensatzes Sätze variabler Länge - im Verwaltungsblock nötig: Satzlänge _l_, um die Länge des Nutzdaten-Bereichs _d_ zu kennen ![](Assets/DBimpl-variable-länge-1.png) - Strategie a) ![](Assets/DBimpl-variable-länge-2.png) - Strategie b) ![](Assets/DBimpl-variable-länge-3.png) Speicherung von Sätzen variabler Länge - Strategie a): Jedes Datenfeld variabler Länge $A_i$ beginnt mit einem _Längenzeiger $al_i$, der angibt, wie lang das folgende Datenfeld ist - Strategie b): Am Beginn des Satzes wird nach dem Satz-Längenzeiger _l_ und der Anzahl der Attribute ein Zeigerfeld $ap_1 ,..., ap_n$ für alle variabel langen Datenfelder eingerichtet - Vorteil Strategie b): leichtere Navigation innerhalb des Satzes (auch für Sätze in Seiten => TID) Anwendung variabel langer Datenfelder - "Wiederholgruppen": Liste von Werten des gleichen Datentyps - Zeichenketten variabler Länge wie _varchar(n)_ sind Wiederholgruppe mit _char_ als Basisdatentyp, mathematisch also die Kleene’sche Hülle $(char)∗$ - Mengen- oder listenwertige Attributwerte, die im Datensatz selbst denormalisiert gespeichert werden sollen (Speicherung als geschachtelte Relation oder Cluster-Speicherung), bei einer Liste von _integer_ -Werten wäre dies $(integer)∗$ - Adressfeld für eine Indexdatei, die zu einem Attributwert auf mehrere Datensätze zeigt (Sekundärindex), also $(pointer)∗$ Blockungstechniken: Nichtspannsätze - jeder Datensatz in maximal einem Block ![](Assets/DBimpl-nichtspannsätze.png) - Standardfall (außer bei BLOBs oder CLOBs) Blockungstechniken: Spannsätze - Spannsätze: Datensatz eventuell in mehreren Blöcken ![](Assets/DBimpl-spannsätze.png) Adressierungstechniken ![](Assets/Dbimpl-adressierungstechniken.png) Adressierung: TID-Konzept - Tupel-Identifier (TID) ist Datensatz-Adresse bestehend aus Seitennummer und Offset - Offset verweist innerhalb der Seite bei einem Offset-Wert von _i_ auf den _i_ -ten Eintrag in einer Liste von Tupelzeigern (Satzverzeichnis), die am Anfang der Seite stehen - Jeder Tupel-Zeiger enthält Offsetwert - Verschiebung auf der Seite: sämtliche Verweise von außen bleiben unverändert - Verschiebungen auf eine andere Seite: statt altem Datensatz neuer TID-Zeiger - diese zweistufige Referenz aus Effizienzgründen nicht wünschenswert: Reorganisation in regelmäßigen Abständen TID-Konzept: einstufige Referenz ![](Assets/DBimpl-tid-einstufig.png) TID-Konzept: zweistufige Referenz ![](Assets/DBimpl-tid-zweistufig.png) ## Alternative Speichermodelle - bisher klassisches N-äres Speichermodell (NSM), auch "row store" - Vorteile: - gesamter Datensatz kann mit einem Seitenzugriff gelesen werden - leichte Änderbarkeit einzelner Attributwerte - Nachteil: - werden nur wenige Attributwerte benötigt, müssen trotzdem immer alle Attributwerte gelesen werden -> unnötiger IO-Aufwand - Alternativen: spaltenorientierte Speichermodelle - Zerlegung einer _n_ -stelligen Relation in eine Menge von Projektionen (z.B. binäre Relation) - Identifikation (und Rekonstruktion) über eine Schlüsselspalte oder Position Spaltenorientierte Datenorganisation ![](Assets/DBimpl-spaltenorientierte-db.png) Alternative Speichermodelle: DSM - Decomposition Storage Model (DSM) -> column stores - alle Werte einer Spalte (Attribut) werden hintereinander gespeichert - Adressierung über Position ![](Assets/DBimpl-dsm.png) - Kompression einfach möglich (z.B. Run length encoding) - effizientere Scanoperationen (Feldoperationen -> bessere Cache-Nutzung) - jedoch: Updateoperationen sind komplexer, Lesen aller Spalten aufwendiger - Einsatz bei leseoptimierten Datenbanken Ein Full-Table-Scan in NSM - Im NSM-Modell stehen alle Tupel einer Tabelle sequenziell hintereinander auf einer Datenbankseite. ![](Assets/Dbimpl-full-table-scan-nsm.png) Ein "Full-Table-Scan" in DSM - Im DSM-Modell stehen alle Werte eines Attributs sequenziell hintereinander auf einer Datenbankseite. ![](Assets/DBimpl-full-table-scan-dsm.png) - Alle Daten, die für den "l_shipdate Scan" geladen werden sind auch dafür relevant. Alternative Speichermodelle: PAX - Partition Attributes Across (PAX) als Kompromiss - NSM: alle Spalten eines Satzes auf der gleichen Seite - DSM: vertikale Partitionierung, Miniseiten für jeweils eine Spalte ![](Assets/DBimpl-PAX.png) ## Main-Memory-Strukturen Speicherstrukturen für Main-Memory-Datenbanken - Vermeidung der seiten-basierten Indirektion (über Seitenadresse, Puffer) - Hauptspeicherzugriffe als neuer Bottleneck ("Memory Wall") - Cache-freundliche Datenstruktur: Hauptspeicherzugriffe tatsächlich nicht byteweise, sondern in Cachelines (64 Bytes) - Speicherlayout: Row Store vs. Column Store - abhängig vom Workload (Reduzierung der Cache Misses) - ggf. Partitionierung für Multicore-Systeme - Kompression der Daten zur Reduktion des Speicherbedarfs - Persistenz weiterhin notwendig, z.B. über Logging - Bsp.: In-Memory-Datenstruktur für relationale Column Stores - pro Spalte = Feld von Attributwerten - Kompression der Attributwerte (siehe Kapitel 8) - ggf. Strukturierung in Segmemten (Chunks) für bessere Speicherverwaltung, NUMA-Effekte ## Speicherorganisation in konkreten DBMS Oracle: Datenbankstruktur ![](Assets/DBimpl-oracle-dbstruktur.png) Oracle: Blöcke ![](Assets/Dbimpl-oracle-blöcke.png) Oracle: Aufbau von Datensätzen ![](Assets/DBimpl-oracle-datensatz.png) - Kettadresse für _Row Chaining_ : Verteilung und Verkettung zu großer Datensätze (> 255 Spalten) über mehrere Blöcke - row id = (data object identifier, data file identifier, block identifier, row identifier) Zusammenfassung - Speicherhierarchie und Zugriffslücke - Speicher- und Sicherungsmedien - Hintergrundspeicher: Blockmodell - Einpassen von Sätzen in Seiten - Satzadressierung: TID-Konzept # Caching und Pufferverwaltung ## Aufgaben Aufgaben der Pufferverwaltung - Puffer: ausgezeichneter Bereich des Hauptspeichers - in Pufferrahmen gegliedert, jeder Pufferrahmen kann Seite der Platte aufnehmen - Aufgaben: - Pufferverwaltung muss angeforderte Seiten im Puffer suchen => effizienteSuchverfahren - parallele Datenbanktransaktionen: geschickte Speicherzuteilung im Puffer - Puffer gefüllt: adäquate Seitenersetzungsstrategien - Unterschiede zwischen einem Betriebssystem-Puffer und _einem Datenbank-Puffer_ - spezielle Anwendung der Pufferverwaltung: Schattenspeicherkonzept ![](Assets/DBimpl-pufferverwaltung.png) Mangelnde Eignung des BS-Puffers - Natürlicher Verbund von Relationen $a$ und $b$ (zugehörige Folge von Seiten: _Ai_ bzw. _Bj_ ) - Implementierung: _Nested-Loop_ ![](Assets/DBimpl-bs-puffer.png) - Ablauf - FIFO: $A_1$ verdrängt, da älteste Seite im Puffer - LRU: $A_1$ verdrängt, da diese Seite nur im ersten Schritt beim Auslesen des ersten Vergleichstupels benötigt wurde - Problem - im nächsten Schritt wird das zweite Tupel von $A_1$ benötigt - weiteres "Aufschaukeln": um $A_1$ laden zu können, muss $B_1$ entfernt werden (im nächsten Schritt benötigt) usw. ## Suche von Seiten und Speicherzuteilung Suchen einer Seite - Direkte Suche: - ohne Hilfsmittel linear im Puffer suchen - Indirekte Suche: - Suche nur noch auf einer kleineren Hilfsstruktur - _unsortierte und sortierte Tabelle_ : alle Seiten im Puffer vermerkt - _verkettete Liste_ : schnelleres sortiertes Einfügen möglich - _Hashtabelle_ : bei geschickt gewählter Hashfunktion günstigster Such- und Änderungsaufwand Speicherzuteilung im Puffer - bei mehreren parallel anstehenden Transaktionen - Lokale Strategien: Jeder Transaktion bestimmte disjunkte Pufferteile verfügbar machen (Größe statisch vor Ablauf der Transaktionen oder dynamisch zur Programmlaufzeit entscheiden) - Globale Strategien: Zugriffsverhalten aller Transaktionen insgesamt bestimmt Speicherzuteilung (gemeinsam von mehreren Transaktionen referenzierte Seiten können so besser berücksichtigt werden) - Seitentypbezogene Strategien: Partition des Puffers: Pufferrahmen für Datenseiten, Zugriffspfadseiten, Data-Dictionary-Seiten, usw. - eigene Ersetzungstrategien für die jeweiligen Teile möglich ## Seitenersetzungsstrategien - Speichersystem fordert Seite $E_2$ an, die nicht im Puffer vorhanden ist - Sämtliche Pufferrahmen sind belegt - vor dem Laden von _E_ 2 Pufferrahmen freimachen - nach den unten beschriebenen Strategien Seite aussuchen - Ist Seite in der Zwischenzeit im Puffer verändert worden, so wird sie nun auf Platte zurückgeschrieben - Ist Seite seit Einlagerung in den Puffer nur gelesen worden, so kann sie überschrieben werden (verdrängt) ![](Assets/DBimpl-Seitenersetzung.png) Seitenersetzung in DBMS - Fixieren von Seiten (Pin oder Fix): - Fixieren von Seiten im Puffer verhindert das Verdrängen - speziell für Seiten, die in Kürze wieder benötigt werden - Freigeben von Seiten (Unpin oder Unfix): - Freigeben zum Verdrängen - speziell für Seiten, die nicht mehr benötigt werden - Zurückschreiben einer Seite: - Auslösen des Zurückschreibens für geänderte Seiten bei Transaktionsende Seitenersetzung: Verfahren - grundsätzliches Vorgehen beim Laden einer Seite: - Demand-paging-Verfahren: genau eine Seite im Puffer durch angeforderte Seite ersetzen - Prepaging-Verfahren: neben der angeforderten Seite auch weitere Seiten in den Puffer einlesen, die eventuell in der Zukunft benötigt werden (z.B. bei BLOBs sinnvoll) - Ersetzen einer Seite im Puffer: - optimale Strategie: Welche Seite hat maximale Distanz zu ihrem nächsten Gebrauch? (nicht realisierbar, zukünftiges Referenzverhalten nicht vorhersehbar) -> Realisierbare Verfahren besitzen keine Kenntnisse über das zukünftige Referenzverhalten - Zufallsstrategie: jeder Seite gleiche Wiederbenutzungswahrscheinlichkeit zuordnen ![](Assets/DBimpl-Seitenersetzung-2.png) Fehlseitenrate $$F=1-p(\frac{1-F_{kalt}}{p_{DB}}) * 100%$$ - $p$: Puffergröße - $p_{DB}$: Puffergröße, die gesamte Datenbank umfasst - $F_{kalt}$: Fehlseitenrate beim Kaltstart (d.h. leerer Puffer) -> Verhältnis von Anzahl der in den Puffer geladenen Seiten zur Anzahl der Referenzierungen - Gute, realisierbare Verfahren sollen vergangenes Referenzverhalten auf Seiten nutzen, um Erwartungswerte für Wiederbenutzung schätzen zu können - besser als Zufallsstrategie - Annäherung an optimale Strategie Merkmale gängiger Strategien - Alter der Seite im Puffer: - Alter einer Seite nach Einlagerung (die globale Strategie (G)) - Alter einer Seite nach dem letztem Referenzzeitpunkt (die Strategie des jüngsten Verhaltens (J)) - Alter einer Seite wird nicht berücksichtigt (-) - Anzahl der Referenzen auf Seite im Puffer: - Anzahl aller Referenzen auf eine Seite (die globale Strategie (G)) - Anzahl nur der letzten Referenzen auf eine Seite (die Strategie des jüngsten Verhaltens (J)) - Anzahl der Referenzen wird nicht berücksichtigt (-) Gängige Strategien ![](Assets/DBimpl-seitenwechsel-strategie.png) Klassifikation gängiger Strategien | Verfahren | Prinzip | Alter | Anzahl | | -------------------------------- | ------------------------------------------------------------------- | ----- | | FIFO | älteste Seite ersetzt | G | - | | LFU (least fre-quently used) | Seite mit geringster Häufigkeit ersetzen | - | G | | LRU (least recently used) | Seite ersetzen, die am längsten nicht referenziert wurde (System R) | J | J | | DGCLOCK (dyn. generalized clock) | Protokollierung der Ersetzungshäufigkeiten wichtiger Seiten | G | JG | | LRD (least reference density) | Ersetzung der Seite mit geringster Referenzdichte | JG | G | Beispiel - Folge von Seitenanforderungen #1, #2 ... - Puffer der Größe 6 ![](Assets/DBimpl-beispiel-seitenwechsel.png) - Ablauf mit - FIFO ... - LFU ... ### LRU: Least Recently Used - Idee: Seite im Puffer ersetzen, die am längsten nicht mehr referenziert wurde - Implementierung: - Liste oder Stack von Seiten - Puffer-Hit bewegt Seite zur MRU-Position (Most Recently Used) - Seite am Ende wird verdrängt ![](Assets/DBimpl-seitenersetzung-lru.png) - Varianten: - durch Interpretation der Pin-Operation: Least Recently Referenced bzw. Least Recently Unfixed - durch Berücksichtigung der letzten $k$ Referenzierungen (d.h. auch Häufigkeit): LRU-K LRU: Probleme - Lock Contention in Multitasking-Umgebungen - Zugriff auf LRU-Liste/Stack und Bewegung der Seite erfordert exklusiven Zugriff auf Datenstruktur - aufwendige Operation - berücksichtigt nur Alter jedoch nicht Häufigkeit - oft gelesene Seiten mit langen Pausen zwischen den Zugriffen werden nicht adäquat berücksichtigt - "Zerstörung" des Puffers durch Scan-Operator - Seiten werden nur einmalig gelesen, verdrängen jedoch andere (ältere) Seiten Lock Contention bei der Pufferverwaltung ![](Assets/DBimpl-lock-contention.png) - Sperren = Latches: leichtgewichtige (wenige CPU-Instruktionen) Objekte für kurzzeitige Sperren Approximierende Verfahren - Idee: - Vereinfachung der benötigten Datenstruktur durch Approximation - Effektivität (Trefferrate) vs. Skalierbarkeit (Anzahl der Threads) - CLOCK: Approximation der Historie durch Bit-Schieberegister der Länge $k$ - $k= 0$: FIFO - $k\rightarrow\infty$: LRU - typisch: $k = 1$ ### CLOCK - Seite mit Benutzt-Bit; bei Referenzierung auf "1" setzen - bei Seitenfehler: - zyklische Suche - Seite mit "0" verdrängen - sonst Setzen auf "0" ![](Assets/DBimpl-seitenersetzung-clock.png) ### GCLOCK - Verbesserung: Benutzt-Bit durch Referenzzähler _RC_ ersetzen; Dekrementierung bei Suche - weitere Verbesserungen: - Initialisierung des Referenzzählers - Inkrementierung des Zählers - seitentypspezifische Maßnahmen (für Typ _i_ : Seitengewicht $E_i$ bei Erstreferenzierung, $W_i$ bei weiterer Referenzierung) - Altern - Varianten: Seite _j_ von Typ _i_ - $GCLOCK(V1): RC_j := E_i ; RC_j := RC_j + W_i$ - $GCLOCK(V2): RC_j := E_i ; RC_j := W_i$ (speziell für $W_i\geq E_i$) ### DGCLOCK - weitere Verbesserung: globaler Zähler $GC$ und Normierung der aktuellen Referenzzähler $RC$ 1. Initialisierung: $RC_j := GC$ 2. Referenzierung von Seite $j : GC := GC + 1 ; RC_j := RC_j + GC$ 3. bei Überschreiten $GC > MIN : \forall j : RC_j := RC_j / C$ ### ARC - Adaptive Replacement Cache: neues Verfahren, das Nachteile von LRU vermeidet - Prinzip: - Puffergröße _c_ - Pufferverzeichnis für 2 _c_ Seiten: _c_ Pufferseiten + _c_ History-Seiten - Liste _L_ 1 : "recency" = kurzfristiger Nutzen-> Seiten, die kürzlich einmal gelesen wurden - Liste _L_ 2 : "frequency" = langfristiger Nutzen -> Seiten, die kürzlich mehrmals gelesen wurden - Ausgangspunkt: einfache Verdrängungsstrategie DBL(2 _c_ ) - Ersetze die LRU-Seite in $L_1$, wenn $|L_1| = c$ , sonst ersetze LRU-Seite in $L_2$ - Ziel: Größenausgleich zwischen $L_1$ und $L_2$ - Zugriff Seite $p$: wenn Treffer -> $p$ wird MRU in $L_2$ , sonst in $L_1$ Von DBL(2c) zu ARC ![](Assets/DBimpl-DBL-to-arc.png) - Parameter $p$ mit $0\leq p \leq c$ - $T_1$ enthält $p$ Seiten, $T_2$ enthält $c-p$ Seiten - Wahl von $p$? ARC: Algorithmus - Seitenanforderungen: $x_1,x_2 ,..., x_t ,...$ - $p = 0, T_1 , B_1 , T_2 ,B_2$ sind initial leer - Fall 1: $x_t \in T_1 \cup T_2$ /* Puffer-Hit */ - Bewege $x_t$ zu MRU von $T_2$ - Fall 2: $x_t \in B_1$ - Anpassung: $p = min\{ p +\delta_1,c\}$ mit $\delta_1 = \begin{cases} 1\quad\text{ wenn } |B_1|\geq |B_2| \\ \frac{|B_2|}{|B_1|} \quad\text{ sonst}\end{cases}$ - $REPLACE(x_t,p)$ - Bewege $x_t$ von $B_1$ zu MRU von $T_2$ - Fall 3: $x_t \in B_2$ - Anpassung: $p = max\{ p - \delta_2, 0 \}$ mit $\delta_2 = \begin{cases} 1\quad\text{ wenn } |B_2|\geq |B_1| \\ \frac{|B_1|}{|B_2|} \quad\text{ sonst}\end{cases}$ - $REPLACE(x_t,p)$ - Bewege $x_t$ von $B_2$ zu MRU von $T_2$ - Fall 4: $x_t \not\in T_1 \cup B_1 \cup T_2 \cup B_2$ - 4.A: $|L_1| = c$ - Wenn $|T_1|p$ oder ($x_t\in B_2$ und $|T_1|=p$) Lösche LRU-Seite in $T_1$ und bewege sie zu MRU in $B_1$ else Lösche LRU-Seite in $T_2$ und bewege sie zu MRU in $B_2$ endif ARC: Beispiel 1. erstmalige Anforderung der Seiten $#1$ und $#2$: Aufnahme in ![](Assets/DBimpl-arc-bsp-1.png) 2. nächsten Referenzierung von $#1$: Übernahme in $T_2$-Liste ![](Assets/DBimpl-arc-bsp-2.png) 3. Seitenanforderungen $#3$, $#4$, $#1$; mit $#2$ wird diese in $T_2$ bewegt; Platz für Seite $#5$: ![](Assets/DBimpl-arc-bsp-3.png) 4. Beantwortung der Seitenanforderungen $#1$ und $#2$ aus $T_2$ 5. neu angeforderten Seiten $#5$ und $#6$ in $T_1$ ![](Assets/DBimpl-arc-bsp-4.png) 6. Seitenanforderung $#7$: Verdrängen von $#4$ aus $T_1$ in $B_1$ ![](Assets/DBimpl-arc-bsp-5.png) ARC: Eigenschaften - kontinuierliche Anpassung von Parameter $p$ - Lernraten $\delta_1$ und $\delta_2$ - "Investieren in Liste mit dem meisten Profit" - Berücksichtigung von Alter und Häufigkeit - durch zwei Listen $L_1$ und $L_2$ - Scan-Resistenz - einmalig gelesene Seiten nur in $L_1$, niemals in $L_2$ - Vermeidung von Lock Contention durch approximierende Varianten (CAR, CART, ...) ## Fazit - Pufferverwaltungsstrategie mit großem Einfluss auf Performance - in kommerziellen Systemen meist LRU mit Variationen - besondere Behandlung von Full-Table-Scans - weiterer Einflussfaktor: Puffergröße - Indikator: Trefferrate (engl. _hit ratio_ ) $$hit\_ratio = \frac{\text{Anz. log. Zugriffe} - \text{Anz. phys. Zugriffe}}{\text{Anz. log. Zugriffe}}$$ - 5-Minuten-Regel (Gray, Putzolu 1997): Daten, die in den nächsten 5 Min. wieder referenziert werden, sollten im Hauptspeicher gehalten werden # Indexierung von Daten ## Klassifikation der Speichertechniken Einordnung in 5-Schichten-Architektur - **Speichersystem** fordert über Systempufferschnittstelle Seiten an - interpretiert diese als interne Datensätze - interne Realisierung der logischen Datensätze mit Hilfe von Zeigern, speziellen Indexeinträgen und weiteren Hilfsstrukturen - Zugriffssystem abstrahiert von der konkreten Realisierung Klassifikation der Speichertechniken - Kriterien für Zugriffsstrukturen oder Zugriffsverfahren: - organisiert interne Relation selbst (Dateiorganisationsform) oder zusätzliche Zugriffsmöglichkeit auf bestehende interne Relation (Zugriffspfad) - Art der Zuordnung von gegebenen Attributwerten zu Datensatz-Adressen: Schlüsselvergleich = Zuordnung von Schlüsselwert zu Adresse über Hilfsstruktur; Schlüsseltransformation = Berechnung der Adresse aus Schlüsselwert (z.B. über Hashfunktion) - Arten von Anfragen, die durch Dateiorganisationsformen und Zugriffspfade effizient unterstützt werden können ![](Assets/DBimpl-speichertechniken-klassifikation.png) Dünn- vs. dichtbesetzter Index - dünnbesetzter Index: nicht für jeden Zugriffsattributwert $k$ ein Eintrag in Indexdatei sondern z.B. nur für _Seitenanführer_ einer sortierten Relation - dichtbesetzter Index: für jeden Datensatz der internen Relation ein Eintrag in Indexdatei Geclusterter vs. nicht-geclusterter Index - geclusterter Index: in der gleichen Form sortiert wie interne Relation - nicht-geclusterter Index: anders organisiert als interne Relation - Primärindex oft dünnbesetzt und geclustert - jeder dünnbesetzte Index ist auch geclusterter Index, aber nicht umgekehrt - Sekundärindex kann nur dichtbesetzter, nicht-geclusterter Index sein (auch: invertierte Datei) ![](Assets/Dbimpl-cluster-vs-nicht-cluster.png) Statische vs. dynamische Struktur - statische Zugriffsstruktur: optimal nur bei bestimmter (fester) Anzahl von verwaltenden Datensätzen - dynamische Zugriffsstruktur: unabhängig von der Anzahl der Datensätze optimal - dynamische Adresstransformationsverfahren verändern dynamisch Bildbereich der Transformation - dynamische Indexverfahren verändern dynamisch Anzahl der Indexstufen => in DBS üblich Klassifikation ![](Assets/DBimpl-zugriff-klassifikation.png) ## Statische Verfahren - Heap, indexsequenziell, indiziert-nichtsequenziell - oft grundlegende Speichertechnik in RDBS - direkte Organisationsformen: keine Hilfsstruktur, keine Adressberechnung (Heap, sequenziell) - statische Indexverfahren für Primärindex und Sekundärindex Statische Verfahren: Überblick ![](Assets/DBimpl-statische-verfahren.png) ### Heap Organisation - völlig unsortiert speichern - physische Reihenfolge der Datensätze ist zeitliche Reihenfolge der Aufnahme von Datensätzen | | | | | | ---- | ----- | ---------- | --- | -------- | | 8832 | Max | Mustermann | ... | 9.1.2003 | | 5588 | Beta | Alpha | ... | 7.3.1978 | | 4711 | Gamma | Delta | ... | 2.5.1945 | Operationen - insert: Zugriff auf letzte Seite der Datei. Genügend freier Platz => Satz anhängen. Sonst nächste freie Seite holen - delete: lookup, dann Löschbit auf 0 gesetzt - lookup: sequenzielles Durchsuchen der Gesamtdatei, maximaler Aufwand (Heap-Datei meist zusammen mit Sekundärindex eingesetzt; oder für sehr kleine Relationen) - Komplexitäten: - Neuaufnahme von Daten $O(1)$ - Suchen $O(n)$ ### Sequenzielle Speicherung - sortiertes Speichern der Datensätze | | | | | | ---- | ----- | ---------- | --- | -------- | | 4711 | Gamma | Delta | ... | 2.5.1945 | | 5588 | Beta | Alpha | ... | 7.3.1978 | | 8832 | Max | Mustermann | ... | 9.1.2003 | Sequenzielle Datei: Operationen - insert: Seite suchen, Datensatz einsortieren => beim Anlegen oder sequenziellen Füllen einer Datei jede Seite nur bis zu gewissem Grad (etwa 66%) füllen - delete: Aufwand bleibt - Folgende Dateiorganisationsformen: - schnelleres lookup - mehr Platzbedarf (durch Hilfsstrukturen wie Indexdateien) - mehr Zeitbedarf bei insert und delete - klassische Indexform: indexsequenzielle Dateiorganisation ## Indexsequenzielle Dateiorganisation - Kombination von sequenzieller Hauptdatei und Indexdatei: indexsequenzielle Dateiorganisationsform - Indexdatei kann geclusterter, dünnbesetzter Index sein - mindestens zweistufiger Baum - Blattebene ist Hauptdatei (Datensätze) - jede andere Stufe ist Indexdatei ![](Assets/DBimpl-indexsequentiell.png) Aufbau der Indexdatei - Datensätze in Indexdatei: _(Primärschlüsselwert, Seitennummer)_ zu jeder Seite der Hauptdatei genau ein Index-Datensatz in Indexdatei - Problem: "Wurzel" des Baumes bei einem einstufigen Index nicht nur eine Seite ![](Assets/DBimpl-indexsequentiell-2.png) Mehrstufiger Index - Optional: Indexdatei wieder indexsequenziell verwalten - Idealerweise: Index höchster Stufe nur noch eine Seite ![](Assets/DBimpl-indexsequentiell-3.png) lookup bei indexsequenziellen Dateien - lookup-Operation sucht Datensatz zum Zugriffsattributwert _w_ - Indexdatei sequenziell durchlaufen, dabei $(v_1,s)$ im Index gesucht mit $v_1\leq w$: - $(v_1,s)$ ist letzter Satz der Indexdatei, dann kann Datensatz zu _w_ höchstens auf dieser Seite gespeichert sein (wenn er existiert) - nächster Satz $(v_2,s′)$ im Index hat $v_2 > w$ , also muss Datensatz zu _w_, wenn vorhanden, auf Seite _s_ gespeichert sein - $(v_1,s)$ überdeckt Zugriffsattributwert _w_ insert bei indexsequenziellen Dateien - insert: zunächst mit lookup Seite finden - Falls Platz, Satz sortiert in gefundener Seite speichern; Index anpassen, falls neuer Satz der erste Satz in der Seite - Falls kein Platz, neue Seite von Freispeicherverwaltung holen; Sätze der "zu vollen" Seite gleichmäßig auf alte und neue Seite verteilen; für neue Seite Indexeintrag anlegen - Alternativ neuen Datensatz auf Überlaufseite zur gefundenen Seite delete bei indexsequenziellen Dateien - delete: zunächst mit lookup Seite finden - Satz auf Seite löschen (Löschbit auf 0) - erster Satz auf Seite: Index anpassen - Falls Seite nach Löschen leer: Index anpassen, Seite an Freispeicherverwaltung zurück Probleme indexsequenzieller Dateien - stark wachsende Dateien: Zahl der linear verketteten Indexseiten wächst; automatische Anpassung der Stufenanzahl nicht vorgesehen - stark schrumpfende Dateien: nur zögernde Verringerung der Index- und Hauptdatei-Seiten - unausgeglichene Seiten in der Hauptdatei (unnötig hoher Speicherplatzbedarf, zu lange Zugriffszeit) Indiziert-nichtsequenzieller Zugriffspfad - zur Unterstützung von Sekundärschlüsseln - mehrere Zugriffpfade dieser Form pro Datei möglich - einstufig oder mehrstufig: höhere Indexstufen wieder indexsequenziell organisiert Aufbau der Indexdatei - Sekundärindex, dichtbesetzter und nicht-geclusteter Index - zu jedem Satz der Hauptdatei Satz $(w,s)$ in der Indexdatei - _w_ Sekundärschlüsselwert, _s_ zugeordnete Seite - entweder für ein _w_ mehrere Sätze in die Indexdatei aufnehmen - oder für ein _w_ Liste von Adresse in der Hauptdatei angeben ![](Assets/Dbimpl-nichtsequentieller-index.png) Operationen - lookup: _w_ kann mehrfach auftreten, Überdeckungstechnik nicht benötigt - insert: Anpassen der Indexdateien - delete: Indexeintrag entfernen Probleme statischer Verfahren - unzureichende Anpassung an wachsende/schrumpfende Datenmengen - schlechte Ausnutzung von Speicher nach Seitensplits - Bevorzugung bestimmter Attribute (Schlüssel) - daher in den folgenden Kapiteln: - bessere Datenstrukturen zur Schlüsselsuche als zusätzlicher Zugriffspfad = Approximation einer Funktion Schlüssel -> Speicheradresse, z.B. über Baumverfahren - Erweiterung von Hashverfahren um Anpassung des Bildbereichs = dynamische Hashverfahren - Behandlung von zusammengesetzten Schlüsseln = multidimensionale Zugriffsverfahren, z.B. multidimensionale Bäume oder raumfüllende Kurven # Baumbasierte Indexstrukturen ## Baumverfahren - Stufenanzahl dynamisch verändern - wichtigste Baumverfahren: B-Bäume und ihre Varianten - B-Baum-Varianten sind noch "allgegenwärtiger" in heutigen Datenbanksystemen als SQL - SQL nur in der relationalen und objektrelationalen Datenbanktechnologie verbreitet; B-Bäume überall als Grundtechnik eingesetzt Baumverfahren: Überblick ![](Assets/DBimpl-Baumverfahren.png) ## B-Bäume - Ausgangspunkt: ausgeglichener, balancierter Suchbaum - Ausgeglichen oder balanciert: alle Pfade von der Wurzel zu den Blättern des Baumes gleich lang - Hauptspeicher-Implementierungsstruktur: binäre Suchbäume, beispielsweise AVL-Bäume von Adelson-Velskii und Landis - Datenbankbereich: Knoten der Suchbäume zugeschnitten auf Seitenstruktur des Datenbanksystems - mehrere Zugriffsattributwerte auf einer Seite - Mehrwegebäume Prinzip des B-Baumes - B-Baum von Bayer (B für balanciert, breit, buschig, Bayer, NICHT: binär) - dynamischer, balancierter Indexbaum, bei dem jeder Indexeintrag auf eine Seite der Hauptdatei zeigt - Mehrwegebaum völlig ausgeglichen, wenn 1. alle Wege von Wurzel bis zu Blättern gleich lang 2. jeder Knoten gleich viele Indexeinträge - vollständiges Ausgleichen zu teuer, deshalb B-Baum-Kriterium: Jede Seite außer der Wurzelseite enthält zwischen $m$ und $2m$ Einträge. Definition B-Baum - Ordnung eines B-Baumes ist minimale Anzahl der Einträge auf den Indexseiten außer der Wurzelseite - Bsp.: B-Baum der Ordnung 8 fasst auf jeder inneren Indexseite zwischen 8 und 16 Einträgen - Def.: Ein Indexbaum ist ein B-Baum der Ordnung $m$ , wenn er die folgenden Eigenschaften erfüllt: 1. Jede Seite enthält höchstens $2m$ Elemente. 2. Jede Seite, außer der Wurzelseite, enthält mindestens $m$ Elemente. 3. Jede Seite ist entweder eine Blattseite ohne Nachfolger oder hat $i+1$ Nachfolger, falls $i$ die Anzahl ihrer Elemente ist. 4. Alle Blattseiten liegen auf der gleichen Stufe. Eigenschaften des B-Baumes - $n$ Datensätze in der Hauptdatei => in $log_m(n)$ Seitenzugriffen von der Wurzel zum Blatt - Durch Balancierungskriterium wird Eigenschaft nahe an der vollständigen Ausgeglichenheit erreicht (1. Kriterium vollständig erfüllt, 2. Kriterium näherungsweise) - Kriterium garantiert 50% Speicherplatzausnutzung - einfache, schnelle Algorithmen zum Suchen, Einfügen und Löschen von Datensätzen (Komplexität von $O(log_m(n))$) Suchen in B-Bäumen - lookup wie in statischen Indexverfahren - Startend auf Wurzelseite Eintrag im B-Baum ermitteln, der den gesuchten Zugriffsattributwert $w$ überdeckt => Zeiger verfolgen, Seite nächster Stufe laden - Suchen: 38, 20, 6 - ![](Assets/DBimpl-b-baum-lookup.png) Einfügen in B-Bäumen - Einfügen eines Wertes _w_ - mit lookup entsprechende Blattseite suchen - passende Seite $n<2m$ Elemente, _w_ einsortieren - passende Seite $n=2m$ Elemente, neue Seite erzeugen, - ersten _m_ Werte auf Originalseite - letzten _m_ Werte auf neue Seite - mittleres Element auf entsprechende Indexseite nach oben - eventuell dieser Prozess rekursiv bis zur Wurzel Einfügen in einen B-Baum: Beispiel ![](Assets/DBimpl-b-baum-beispiel.png) ![](Assets/DBimpl-b-baum-beispiel-2.png) Löschen in B-Bäumen - bei weniger als $m$ Elementen auf Seite: Unterlauf - Löschen eines Wertes $w$ : Bsp.: 24; 28, 38, 35 - mit lookup entsprechende Seite suchen - $w$ auf Blattseite gespeichert => Wert löschen, eventuell Unterlauf behandeln - $w$ nicht auf Blattseite gespeichert => Wert löschen, durch lexikographisch nächstkleineres Element von einer Blattseite ersetzen, eventuell auf Blattseite => Unterlauf - Unterlaufbehandlung - Ausgleichen mit der benachbarten Seite (benachbarte Seite $n$ Elemente mit $n>m$) - oder Zusammenlegen zweier Seiten zu einer (Nachbarseite $n=m$ Elemente), das "mittlere" Element von Indexseite darüber dazu, auf Indexseite eventuell => Unterlauf - Einfügen des Elementes 22; Löschen von 22 ![](Assets/DBimpl-b-baum-beispiel-3.png) Komplexität der Operationen - Aufwand beim Einfügen, Suchen und Löschen im B-Baum immer $O(log_m(n))$ Operationen - entspricht genau der "Höhe" des Baumes - Konkret: Seiten der Größe 4 KB, Zugriffsattributwert 32 Bytes, 8-Byte-Zeiger: zwischen 50 und 100 Indexeinträge pro Seite; Ordnung dieses B-Baumes 50 - 1.000.000 Datensätze: $log_{50}(1000000) = 4$ Seitenzugriffe im schlechtesten Fall - Wurzelseite jedes B-Baumes normalerweise im Puffer: 3 Seitenzugriffe ## B+-Baum Varianten - B+-Bäume: nur Blattebene enthält Daten -> Baum ist hohl - B∗-Bäume: Aufteilen von Seiten vermeiden durch "Shuffle" - Präfix-B-Bäume: Zeichenketten als Zugriffsattributwerte, nur Präfix indexieren B+-Baum: Motivation - B-Baum: Wie/wo werden Nicht-Schlüsseldaten (Tupel, TIDs) gespeichert? - Zusammen mit Schlüsseln in allen Knoten? - Problem beim Traversieren des Baumes, z.B. bei Bereichsanfragen ![](Assets/DBimpl-b+-baum-traversieren.png) B+-Baum - in der Praxis am häufigsten eingesetzte Variante des B-Baumes: - effizientere Änderungsoperationen, insb. Löschen - Verringerung der Baumhöhe - Änderungen gegenüber B-Baum - in inneren Knoten nur noch Zugriffsattributwert und Zeiger auf nachfolgenden Seite der nächsten Stufe; nur Blattknoten enthalten neben Zugriffsattributwert die Daten (Datensätze bzw. Verweise auf Datensätze in der Hauptdatei) - Knoten der Blattebene sind untereinander verkettet für effiziente Unterstützung von Bereichsanfragen B+-Baum: Aufbau ![](Assets/Dbimpl-b+-baum-aufbau.png) Ordnung; Operationen - Ordnung für B+-Baum: $(x,y), x$ Mindestbelegung der Indexseiten, $y$ Mindestbelegung der Blattseiten - delete gegenüber B-Baum effizienter ("Ausleihen" eines Elementes von der Blattseite entfällt) - Zugriffsattributwerte in inneren Knoten können sogar stehenbleiben - häufig als Primärindex eingesetzt - B+-Baum ist dynamische, mehrstufige, indexsequenziellen Datei B+-Baum: Blattebene mit Verweisen ![](Assets/DBimpl-b+-baum-blattebenen.png) B+-Baum: Datenstrukturen ```cpp BPlusBranchNode =record of nkeys: int; ptrs: array[0 .. nkeys ] of PageNum; keys: array[1 .. nkeys ]of KeyType; level: int; /* Level= 1 zeigt an, dass die ptrs auf Blätter zeigen */ end; ``` ```cpp BPlusLeafNode = record of nkeys: int; keys: array[1 .. nkeys ]of KeyType; payload: array[1 .. nkeys ]of LoadType ; /* Daten bzw. TIDs */ nextleaf: PageNum; end; ``` Operationen im B+-Baum - lookup: wie im B-Baum jedoch immer bis zur Blattebene - $search(u,o)$: Lookup für unteren Wert $u$ , Traversieren auf der Blattebene bis zum oberen Wert $o$ - $insert$: ähnlich zum B-Baum - im Fall des Split bei Überlaufbehandlung wird nur ein Separatorschlüssel im Elternknoten eingefügt - z.B. Kopie des kleinsten Schlüsselwertes des "rechten" Kindknotens oder geeigneter Wert zwischen beiden Knoten - $delete$: ähnlich zum B-Baum, jedoch - Löschen der Daten zunächst nur auf Blattebene - bei Unterlauf: entweder Ausgleich mit Nachbarknoten oder Vereinigen mit Nachbarknoten; ggf. Anpassen (Ausgleich) oder Löschen (Vereinigen) des Separatorschlüssels - alternativ: Unterlauf akzeptieren und durch spätere inserts oder Reorganisation auflösen Weitere Entwurfsentscheidungen für B+-Baum - Schlüsselsuche im Knoten: sequenzielle Suche vs. binäre Suche vs. Interpolation Search - Knotengröße: Maximieren der Anzahl der Vergleiche (Knotennutzen) pro I/O-Zeiteinheit | Seitengröße (KB) | Sätze/Seite | Knotennutzen | I/O-Zeit (ms) | Nutzen/Zeit | | ---------------- | ----------- | ------------ | ------------- | ----------- | | 4 | 143 | ≈7 | 5,020 | 1,427 | | 16 | 573 | ≈ 9 | 5,080 | 1,804 | | 64 | 2.294 | ≈ 11 | 5,320 | 2,098 | | 128 | 4.588 | ≈ 12 | 5,640 | 2,157 | | 256 | 9.175 | ≈ 13 | 6,280 | 2,096 | | 1024 | 36.700 | ≈ 15 | 10,120 | 1,498 | | 4096 | 146.801 | ≈ 17 | 25,480 | 0,674 | [Literatur](G. Graefe: Modern B-Tree Techniques, Foundations and Trends in Databases, 2010) - Konsistenzprüfung während der Traversierung - mögliche Inkonsistenzen durch Speicherfehler, konkurrierende Änderungen + Implementierungsfehler, ... - Cache-Optimierung: Knotenstruktur, Kompression (Präfix-/Suffix Truncation) - Pointer Swizzling: zur Ersetzung der logischen Seitennummern durch physische Adressen - Knotengröße: bei großen Puffern kann großer Teil der inneren Knoten im Puffer gehalten werden -> größere Knoten (im MB-Bereich) sinnvoll - Verzögern des Splits bei Überlaufbehandlung: Ausgleichen mit Nachbarknoten soweit möglich; verbessert Auslastung der Knoten - zusätzliche Verweise zwischen Knoten - z.B. zwischen inneren Knoten der gleichen Ebene, zu Elternknoten, etc. - ermöglichen Konsistenzchecks, aber erschweren Sperren für Nebenläufigkeitskontrolle ## Weitere Varianten Präfix-B-Baum - B-Baum über Zeichenkettenattribut - lange Schlüssel in inneren Knoten -> hoher Speicherplatzbedarf - vollständige Schlüssel eigentlich nicht notwendig, da nur "Wegweiser" - Idee: Verwaltung von Trennwerten -> Präfix-B-Baum - in inneren Knoten nur Trennwerte, die lexikographisch zwischen den Werten liegen - möglichst kurze Trennwerte, z.B. kürzester eindeutiger Präfix ![](Assets/DBimpl-präfix-b-baum.png) - aber: Beispiel "Vandenberg" und "Vandenbergh" Mehr-Attribut-B-Baum - B-Baum ist eindimensionale Struktur, jedoch können mehrere Attribute als kompositer Schlüssel indexiert werden `create index NameIdx on KUNDE(Name, Vorname)` - allerdings: Attribute werden bei partial-match-Anfragen nicht gleich behandelt! - Alternative: raumfüllende Kurven -> multidimensionale Indexstrukturen ![](Assets/Dbimpl-mehr-attribut-b-baum.png) ## Optimierungen für moderne Hardware Optimierungspotential - Verbesserung der Cache-Trefferrate - Organisation der Datenstruktur entsprechend Cacheline (Größe, Anordnung der Daten) - In-Place-Update im Hauptspeicher -> Cache-Invalidierung: verändertes Update Handling - Pointer Swizzling - Berücksichtigung der Storage-Eigenschaften - SSD vs. HDD - Bevorzugung sequentieller Schreiboperationen - spezielle Berücksichtigung für Main-Memory-Indexe - Synchronisation in Multicore-Umgebungen - Lock/Latch-freie Operationen CSB+-Baum - =Cache Sensitive B+ Tree (Rao, Ross: Making B+-Trees Cache Conscious in Main Memory, SIGMOD 2000) - "Cache-Freundlichkeit" durch 1. Platzsparen im Knoten -> mehr relevante Daten im Cache 2. Eliminieren von Zeigern für 1. und Reduzierung von Zeigerarithmetik - Ansatz: veränderte Struktur der inneren Knoten - Zeiger auf ersten Kindknoten - alle Kindknoten eines Knotens sind in einem zusammenhängenden Speicherbereich (Knotengruppe) allokiert und werden über einenOffsetadressiert ![](Assets/DBimpl-csb-baum.png) Bw-Baum - = Buzzword Tree (Levandoski, Lomet, Sengupta: The Bw-Tree: A B-tree for New Hardware Platforms, ICDE 2013) - Ziele: Cache-Freundlichkeit, Multicore-CPUs, Flash-Speichereigenschaften - Techniken - überwiegend Latch-freie Operationen, stattdessen atomare CAS-Instruktionen - spezifische Struktur-Modifikationsoperationen: Folge von atomaren Modifikationen, Blink-ähnliche Struktur - Delta-Updates und Log Structured Storage (LSS) - Änderungen an Seiten/Knoten werden nicht direkt ausgeführt, sondern in Delta-Records pro Knoten erfasst - keine Synchronisation für Zugriff auf Seiten notwendig - Seite kann auch nach Änderung im Cache verbleiben - Seiten + Delta Records werden periodisch konsolidiert - Log Structured Storage -> später! - Mapping-Tabelle: logische Seitennummern in - (a) Offset im Flash-Speicher - (b) Zeiger im Hauptspeicher ## LSM-Baum - = Log Structured Merge Tree (O’Neil, Cheng, Gawlick, O’Neil: The log-structured merge-tree (LSM-tree). Acta Informatica. 33 (4): 351-385, 1996) - Ziel: höherer Schreibdurchsatz durch Eliminierung verstreuter In-Place-Updates - Einsatz in diversen NoSQL-Systemen: HBase, Cassandra, BigTable, LevelDB, RocksDB, ... - Grundidee - Batches von Schreiboperationen werden sequentiell in Indexdateien gespeichert, d.h. Sortieren vor Schreiben auf Externspeicher (Log Structured) - Neue Updates werden in neuen Indexdateien gespeichert - Indexdateien werden periodisch zusammengefügt (Merge) - Leseoperationen müssen alle Indexdateien konsultieren ![](Assets/DBimpl-LSM-baum.png) LSM-Baum: Realisierung - Main-Memory-Baum $C_0$ als Puffer, sortiert nach Schlüsseln, z.B. als AVL- oder RB-Baum - bei Erreichen eines gegebenen Füllgrads -> Herausschreiben auf Disk (siehe unten) - ergänzt um Write Ahead Logging auf Disk für Wiederherstellung nach Systemfehler - mehrere Append-only, disk-basierte Indexe $C_1, C_2,...,$ ebenfalls sortiert nach Schlüssel (z.B. als B-Baum) - effiziente Unterstützung von Scans (Schlüsselsuche) - Merge in einem Schritt - Aktualität der Indexe: $C_0,C_1,...,C_n$ LSM-Baum: Verdichtung - wenn bestimmte Anzahl von Dateien erzeugt wurden (z.B. 5 Dateien je 100 Datensätze), werden diese in eine Datei gemischt (1 Datei mit 500 Sätzen) - sobald 5 Dateien mit 500 Sätzen vorliegen, dann Mischen in eine Datei - usw. - Nachteil: große Anzahl von Dateien, die alle durchsucht werden müssen LSM-Baum: Ebenenweise Verdichtung 1. pro Ebene wird eine bestimmte Zahl von Dateien verwaltet, partitioniert nach Schlüsseln (keine Überlappung der Schlüssel) -> Suche nur in einer Datei notwendig 2. Ausnahme: erste Ebene (Überlappung erlaubt) 3. Mischen der Dateien jeweils in die nächsthöhere Ebene: Auswahl einer Datei und Aufteilen der Sätze -> Platz für neue Daten schaffen LSM-Baum: Lesezugriffe - grundsätzlich: Verbesserung der Schreibperformance zulasten der Leseperformance - Suche in allen Indexen $C_0,C_1,...,C_n$ notwendig - Vermeiden unnötiger Lesevorgänge durch Bloom-Filter pro Index oder pro Run - (probabilistische) Datenstruktur zum Feststellen, ob Objekt in einer Menge enthalten ist - Bit-Feld: Objekt wird über $k$ Hashfunktionen auf $k$ Bits abgebildet, die auf 1 gesetzt werden - Prüfen auf Enthaltensein: Hashfunktionen anwenden -> Wenn alle $k$ Bits $= 1$, dann ist Objekt enthalten - aber falsch-positive Werte möglich! - können ebenfalls durch Mischen kombiniert werden - [Quelle](Bloom: Space/Time Trade-offs in Hash Coding with Allowable Errors. CACM, 13(7):422-426, 1970) ## Zusammenfassung - B+-Baum als "Arbeitspferd" für Indexing - Standardoperationen: Suche, Einfügen, Löschen - noch nicht betrachtet: Nebenläufigkeitskontrolle und Wiederherstellung im Fehlerfall - diverse Varianten und Optimierungen - LSM-Baum für schreibintensive Workloads # Hashing ## Hashing - Zugriff über Adressberechnung aus Schlüssel - linearer Adressraum der Grösse $n$ - Adressierung in einem Array - Ziel: direkter Zugriff in $O(1)$ statt logarithmisch wie bei Bäumen Hashverfahren - Schlüsseltransformation und Überlaufbehandlung - DB-Technik: - Disk-basiert: Bildbereich entspricht Seiten-Adressraum - Hauptspeicher: Adresse in einem Array (Hauptspeicheradresse plus Offset) - Dynamik: dynamische Hashfunktionen oder Re-Hashen Grundprinzipien - Basis-Hashfunktion: $h(k)= k mod m$ - $m$ oft Primzahl da besseres Verhalten bei Kollisionen - oder $m=2^k$ aufgrund einfacher Berechnungen - Überlauf-Behandlung - Überlaufseiten als verkettete Liste - lineares Sondieren - quadratisches Sondieren - doppeltes Hashen - ... Hashverfahren für blockorientierte Datenhaltung ![](Assets/Dbimpl-hashverfahren.png) Operationen und Zeitkomplexität - lookup, modify, insert, delete - lookup benötigt maximal $1+ #B(h(w))$ Seitenzugriffe - $#B(h(w))$ Anzahl der Seiten (inklusive der Überlaufseiten) des Buckets für Hash-Wert $h(w)$ - Untere Schranke 2 (Zugriff auf Hashverzeichnis plus Zugriff auf erste Seite) Statisches Hashen: Probleme - mangelnde Dynamik - Vergrößerung des Bildbereichs erfordert komplettes Neu-Hashen - Wahl der Hashfunktion entscheidend; - Bsp.: Hash-Index aus 100 Buckets, Studenten über 6-stellige MATRNR (wird fortlaufend vergeben) hashen - ersten beiden Stellen: Datensätze auf wenigen Seiten quasi sequenziell abgespeichert - letzten beiden Stellen: verteilen die Datensätze gleichmäßig auf alle Seiten - Sortiertes Ausgeben einer Relation nicht unterstützt ## Hash-Funktionen - klassisch, etwa Divisions-Rest-Methode $h() = x mod m$ - zusammengesetzt, etwa $h(k)= h_2 (h_1 (k))$ (siehe später Spriralhashen) - ordnungserhaltend $k_1 < k_2 => ( h(k_1) = h(k_2) \vee h(k_1) < h(k_2))$ - dynamisch (siehe später) - mehrdimensional (siehe später) - materialisiert (etwa Dictionary Encoding, siehe später) Ordnungserhaltenes Hashen - Schlüsselwerte werden als 8-Bit-Integer-Werte ohne Vorzeichen kodiert und sind gleichmässig im Bereich $0...2^8-1$ verteilt. - Die Extraktion der ersten drei Bits ergibt eine ordnungserhaltende Hashfunktion für den Bereich $0...2^3-1$. - Sind die Schlüsselwerte nicht gleichverteilt, etwa weil es sich um fortlaufend vergebene Nummern handelt, ist das Ergebnis zwar weiterhin ordnungserhaltend, aber die Hash-Tabelle ist sehr ungleichmäßig gefüllt. ## Hardware-sensitives Hashen Neue Hardware und Hash-Funktionen - Beobachtung: Hashen mit klassischem Sondieren ungünstig für neue Hardware - schwer parallelisierbar - Clustern von Werten verletzt Nähe der Werte (bei Cache Lines) - Varianten versuchen beide Punkte anzugehen - Cuckoo-Hashing - optimiertes lineares Sondieren - Hopscotch-Hashing - Robin-Hood-Hashing Cuckoo-Hashen - Kuckucks-Hashen - soll Parallelität erhöhen im Vergleich zu linearem Sondieren - Idee: Zwei Tabellen mit zwei Hash-Funktionen - im Fall einer Kollision in einer Tabelle wird in der zweiten Tabelle gesucht - ist dort der Platz belegt, wird der dortige Eintrag verdrängt in die jeweils andere Tabelle - _Kuckuck wirft Ei aus dem Nest_ - dies wird solange gemacht bis ein freier Platz gefunden wird - Beispiel - zwei einfache Hash-Funktionen, die jeweils die letzte beziehungsweise vorletzte Dezimalstelle einer Zahl extrahieren $h_1(k) = k mod 10$ $h_2(k) = (k/10) mod 10$ - Bei einer Suche muss immer in beiden Tabellen nachgeschaut werden, also $T_1[h_1(k)] = k\vee T_2[h_2(k)] = k$. - Wir fügen die Zahlen $433, 129$ und $555$ in die Tabelle $T_1$ ein. Beim Einfügen von $783$ ist der Platz in Tabelle $T_1$ belegt, so dass diese Zahl in $T_2$ gespeichert werden muss. Wird nun mit $103$ eine weitere Zahl eingefügt, die mit $433$ unter $h_1$ kollidiert, ist dies mit $h_2$ weiterhin möglich. Cuckoo Beispiel - Ergebnis des Einfügens von 433, 129 , 555 , 783 , 103 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | ----- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | $T_1$ | | | | 433 | | 555 | | | | 129 | | $T_2$ | 103 | | | | | | | | 783 | - Wird nun die Zahl $889$ eingefügt, so sind beide möglichen Positionen belegt. $889$ kann in $T_1$ die dort stehende Zahl $129$ verdrängen, die in $T_2$ an der Position $T_2[2]$ gespeichert werden kann. | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | ----- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | $T_1$ | | | | 433 | | 555 | | | | 129 | | $T_2$ | 103 | | 129 | | | | | | 783 | - Wird nun $789$ eingefügt, sind wiederum beide Positionen belegt. Das Verdrängen von $889$ aus $T_1$ würde zu einem kaskadierenden Verdrängen führen: $889$ würde in $T_2$ dann $783$ verdrängen, das wiederum $433$ in $T_1$ verdrängen würde. Dies würde gehen da $433$ an der Stelle $T_2[3]$ Platz hätte | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | ----- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | $T_1$ | | | | 783 | | 555 | | | | 129 | | $T_2$ | 103 | | 129 | 433 | | | | | 783 | Weitere Prinzipien der Optimierung - Lokalität von Datenzugriffen verringert die Wahrscheinlichkeit von Cache Misses - Blockung von Daten kann an die Grösse von Cache-Lines (64 Bytes) angepasst werden, und erhöht den Durchsatz - Parallelisierung für SIMD basierend auf einer Vektorisierung der Daten kann insbesondere SIMD-basierte Co-Prozessoren gut ausnutzen, aber greift auch bei MICs Optimiertes lineares Sondieren - Lineares Sondieren ist gut geeignet, um den Sondierungsvorgang auf Vektoren zu parallelisieren - Suchschlüssel kann in einen Vektor der Länge $n$ an alle Positionen kopiert werden - beginnend ab derem initialen Sondierungspunkt $h(k)$ können dann Vektoren jeweils mit Vektoren aus der Hash-Tabelle verglichen werden, also zuerst mit $H[h)k),...,h(k)+n - 1]$, dann mit $H[h(k) + n,...,h(k) + 2n-1 ]$, etc. - Vergleich kann parallel erfolgen; muss sowohl auf Vorhandensein von $k$ als auch auf Existenz einer leeren Position prüfen Hopscotch-Hashen - Hopscotch: _Himmel und Hölle_ beziehungsweise _wild herumhopsen_ - Begrenzung des Sondierungsraum auf eine (konstante) Länge - Idee: - beim Einfügen erfolgt die Suche (parallel) in der festen Nachbarschaft - wird Schlüssel $k$ nicht gefunden und existiert kein freier Slot in der festen Nachbarschaft, dann wird versucht, $k$ mit einem anderen Schlüssel aus der festen Nachbarschaft zu tauschen - dafür wird die nächste freie Stelle gesucht; von dieser wird rückwärts in Richtung $h(k)$ gesucht und jeder Eintrag $k′$ untersucht - wenn die aktuelle freie Stelle noch in der festen Nachbarschaft von $k′$ liegt, wird getauscht: $k′$ springt auf die freie Stelle Robin-Hood-Hashen - Robin-Hood: _Nimm von den Reichen gib es den Armen_ - Basisidee: in der Situation, dass beim Sondieren für $k$ ein Platz bereits mit einem Element $k′$ besetzt ist, wird der nächste Sondierungsschritt mit demjenigen Element weitergeführt, das die kleinere Distanz zum eigentlichen Hash-Wert $h(k)$ bzw. $h(k′)$ hat ## Dynamische Hash-Verfahren Lineares Hashen - Folge von Hash-Funktionen, die wie folgt charakterisiert sind: - $h_i$: dom(Primärschlüssel) ->$\{0,..., 2^i \times N\}$ ist eine Folge von Hash-Funktionen mit $i\in\{0,1,2,...\}$ und $N$ als Anfangsgröße des Hash-Verzeichnisses - Wert von $i$ wird auch als Level der Hash-Funktion bezeichnet - $dom(Primärschlüssel)$ wird im folgenden als $dom(Prim)$ abgekürzt - Für diese Hash-Funktionen gelten die folgenden Bedingungen: - $h_{i+1}(w) = h_i(w)$ für etwa die Hälfte aller $w\in dom(Prim)$ - $h_{i+1}(w) = h_i(w) + 2^i\times N$ für die andere Hälfte - Bedingungen sind zum Beispiel erfüllt, wenn $h_i(w)$ als $w mod(2^i\times N)$ gewählt wird - Darstellung durch Bit-Strings, Hinzunahme eines Bits verdoppelt Bildbereich Prinzip lineares Hashen - für ein $w$ höchstens zwei Hash-Funktionen zuständig, deren Level nur um 1 differiert, Entscheidung zwischen diesen beiden durch Split-Zeiger - $sp$ Split-Zeiger (gibt an, welche Seite als nächstes geteilt wird) - $lv$ Level (gibt an, welche Hash-Funktionen benutzt werden) - Aus Split-Zeiger und Level läßt sich die Gesamtanzahl $Anz$ der belegten Seiten wie folgt berechnen: - $Anz = 2^{lv} + sp$ - Beide Werte werden am Anfang mit 0 initialisiert. ![](Assets/DBimpl-lineares-hashen.png) Lookup - $$s := h_{lv}(w)$; - if $s < sp$ - then $s := h_{lv + 1}(w)$; - zuerst Hash-Wert mit der "kleineren" Hash-Funktion bestimmen - liegt dieser unter dem Wert des Split-Zeigers => größere Hash-Funktion verwenden Splitten einer Seite 1. Die Sätze der Seite (Bucket), auf die $sp$ zeigt, werden mittels $h_{lv+1}$ neu verteilt (ca. die Hälfte der Sätze wird auf Seite (Bucket) unter Hash-Nummer $2^{lv}*N +sp$ verschoben) 2. Der Split-Zeiger wird weitergesetzt: $sp:=sp +1;$ 3. Nach Abarbeiten eines Levels wird wieder bei Seite 0 begonnen; der Level wird um 1 erhöht: ``` if sp = 2^{lv} * N then begin lv := lv + 1 ; sp := 0 end; ``` ![](DBimpl-hashing-split.png) Problem lineares Hashen ![](Assets/DBimpl-lineares-hashing-problem.png) Erweiterbares Hashen - Problem: Split erfolgt an fester Position, nicht dort wo Seiten überlaufen - Idee: binärer Trie zum Zugriff auf Indexseiten - Blätter unterschiedlicher Tiefe - Indexseiten haben Tiefenwert - Split erfolgt bei Überlauf - aber: Speicherung nicht als Trie, sondern als Array - entspricht vollständigem Trie mit maximaler Tiefe - "shared" Seiten als Blätter - Array der Grösse 2 _d_ für maximale Tiefe _d_ - erfordert nun nur einen Speicherzugriff! - bei Überlauf: Indexgrösse muss möglicherweise verdoppelt werden! - Ausgangslage: - Einfügen von 00111111 würde Überlauf bei erreichter maximaler Tiefe erzeugen ![](Assets/DBimpl-erweiterbares-hashing.png) - Verdopplung der Indexgrösse ![](Assets/DBimpl-erweiterbares-hashing-2.png) - nun möglich: Split der Seite ![](Assets/DBimpl-erweiterbares-hashing-3.png) Variante: Array als Trie gespeichert ![](Assets/DBimpl-trie.png) Spiral-Hashen - Problem: zyklisch erhöhte Wahrscheinlichkeit des Splittens - Lösung: unterschiedliche Dichte der Hashwerte - Interpretation der Bit-Strings als binäre Nachkommadarstellung einer Zahl zwischen $0.0$ und $1.0$ - Funktion von $[0.0,1.0] -> [0.0,1.0]$ so dass Dichte gleichmässig verteilter Werte nahe $1.0$ doppelt so gross ist wie nahe $0.0$ - Umverteilung mittels Exponentialfunktion - Funktion $exp(n)$ $exp(n) = 2^n - 1$ erfüllt die Bedingungen - insbesondere gilt $2^0 - 1 = 0$ und $2^1 - 1 = 1$ - Hashfunktion exhash $exhash(k) = exp(h(k)) = 2^{h(k)} - 1$ - Wirkung der verwendeten Hashfunktion im Intervall $0.0$ bis $1.0$ | $n$ | $2^n-1$ | | ----- | ----------- | | $0.0$ | $0.0$ | | $0.1$ | $0.0717735$ | | $0.2$ | $0.1486984$ | | $0.3$ | $0.2311444$ | | $0.4$ | $0.3195079$ | | $0.5$ | $0.4142136$ | | $0.6$ | $0.5157166$ | | $0.7$ | $0.6245048$ | | $0.8$ | $0.7411011$ | | $0.9$ | $0.866066$ | | $1.0$ | $1.0$ | - Spiralförmiges Ausbreiten - Ausgangslage: 4 Seiten der Tiefe 2 ![](Assets/DBimpl-spiral-hashing.png) - Spiralförmiges Ausbreiten - Split der Seite mit der höchsten Dichte - Ergebnis: 5 Seiten, davon 3 der Tiefe 2 und 2 der Tiefe 3 ![](Assets/DBimpl-spiral-hashing-2.png) ## Grid-File Grid-Files - bekannteste und von der Technik her attraktive mehrdimensionale Dateiorganisationsform - eigene Kategorie: Elemente der Schlüsseltransformation wie bei Hashverfahren und Indexdateien wie bei Baumverfahren kombiniert - deshalb hier bei Hash-Verfahren betrachtet Grid-File: Zielsetzungen - Prinzip der 2 Plattenzugriffe: Jeder Datensatz soll bei einer _exact-match_ -Anfrage in 2 Zugriffen erreichbar sein - Zerlegung des Datenraums in Quader: _n_ -dimensionale Quader bilden die Suchregionen im Grid-File - Prinzip der Nachbarschaftserhaltung: Ähnliche Objekte sollten auf der gleichen Seite gespeichert werden - Symmetrische Behandlung aller Raum-Dimensionen: _partial-match_ -Anfragen ermöglicht - Dynamische Anpassung der Grid-Struktur beim Löschen und Einfügen Prinzip der zwei Plattenzugriffe - bei exact-match 1. gesuchtes $k$ -Tupel auf Intervalle der Skalen abbilden; als Kombination der ermittelten Intervalle werden Indexwerte errechnet; Skalen im Hauptspeicher => noch kein Plattenzugriff 2. über errechnete Indexwerte Zugriff auf das _Grid-Directory_ ; dort Adressen der Datensatz-Seiten gespeichert; erster _Plattenzugriff_. 3. Der Datensatz-Zugriff: zweiter _Plattenzugriff_. Aufbau eines Grid-Files ![](Assets/DBimpl-grid-files.png) - Grid: $k$ eindimensionale Felder (Skalen), jede Skala repräsentiert Attribut - _Skalen_ bestehen aus Partition der zugeordneten Wertebereiche in Intervalle - Grid-Directory besteht aus Grid-Zellen, die den Datenraum in Quader zerlegen - Grid-Zellen bilden eine Grid-Region, der genau eine Datensatz-Seite zugeordnet wird - Grid-Region: $k$-dimensionales, konvexes (Regionen sind paarweise disjunkt) Operationen - Zu Anfang: Zelle = Region = eine Datensatz-Seite - Seitenüberlauf: - Seite wird geteilt - falls zugehörige Gridregion aus nur einer Gridzelle besteht, muss ein Intervall auf einer Skala in zwei Intervalle unterteilt werden - besteht Region aus mehreren Zellen, so werden diese Zellen in einzelne Regionen zerlegt - Seitenunterlauf: - Zwei Regionen zu einer zusammenfassen, falls das Ergebnis eine neue, konvexe Region ergibt Beispiel - Start-Grid-File ![](Assets/DBimpl-grid-start.png) - Datensätze einfügen: $(45,D),(2,B),(87,S),(75,M),(55,K),(3,Y),(15,D),(25,K),(48,F)$ - jede Seite des Grid-Files fasst bis zu drei Datensätze - Eingefügt: $(45, D), (2, B), (87, S)$ ![](Assets/DBimpl-grid-beispiel-1.png) - Einfügen von $(75, M)$ erzwingt Split ![](Assets/Dbimpl-grid-beispiel-2.png) - Eingefügt: $(55, K)$ ![](Assets/Dbimpl-grid-beispiel-3.png) - Einfügen von $(3, Y)$ erzwingt wiederum einen Split ![](Assets/Dbimpl-grid-beispiel-4.png) - Eingefügt: (15, D), (25, K), ![](Assets/DBimpl-grid-beispiel-5.png) - Einfügen von (48, F) erzwingt wiederum einen Split ![](Assets/DBimpl-grid-beispiel-6.png) Buddy-System - Beschriebenes Verfahren: Buddy-System (Zwillings-System) - Die im gleichen Schritt entstandenen Zellen können zu Regionen zusammengefasst werden; Keine andere Zusammenfassung von Zellen ist im Buddy-System erlaubt - Unflexibel beim Löschen: nur Zusammenfassungen von Regionen erlaubt, die vorher als Zwillinge entstanden waren - Beispiel: $(15,D)$ löschen: Seiten 1 und 4 zusammenfassen; $(87,S)$ löschen, Seite 2 zwar unterbelegt, kann aber mit keiner anderen Seite zusammengefasst werden # Weitere Indexstrukturen ## Bitmap-Indexe Bitmap-Indexe - Idee: _Bit-Vektor_ zur Kodierung der Tupel-Attributwert-Zuordnung - Vergleich mit baumbasierten Indexstrukturen: - vermeidet degenerierte B-Bäume - unempfindlicher gegenüber höherer Zahl von Attributen - einfachere Unterstützung von Anfragen, in denen nur einige (der indexierten) Attribute beschränkt werden - dafür aber i.allg. höhere Aktualisierungskosten - beispielsweise in Data Warehouses wegen des überwiegend lesenden Zugriffs unproblematisch Bitmap-Index: Realisierung - Prinzip: Ersetzung der TIDs (rowid) für einen Schlüsselwert im $b$ +-Baum durch Bitvektor - Knotenaufbau: $|B: 0 1001 0...0 1 | F: 1 01 000... 10 | O: 000 101 ...00 |$ - Vorteil: geringerer Speicherbedarf - Beispiel: 150.000 Tupel, 3 verschiedene Schlüsselwerte, 4 Byte für TID - B+-Baum: 600 KB - Bitmap: $3*18750 Byte =56KB$ - Nachteil: Aktualisierungsaufwand - Definition in Oracle ```sql CREATE BITMAP INDEX bestellstatus_idx ON bestellung(status); ``` - Speicherung in komprimierter Form Standard-Bitmap-Index - jedes Attribut wird getrennt abgespeichert - für jede Ausprägung eines Attributs wird ein Bitmap-Vektor angelegt: - für jedes Tupel steht ein Bit, dieses wird auf 1 gesetzt, wenn das indexierte Attribut in dem Tupel den Referenzwert dieses Bitmap-Vektors enthält - die Anzahl der entstehenden Bitmap-Vektoren pro Dimension entspricht der Anzahl der unterschiedlichen Werte, die für das Attribut vorkommen - Beispiel: Attribut Geschlecht - 2 Wertausprägungen (m/w) - 2 Bitmap-Vektoren | PersId | Name | Geschlecht | Bitmap-w | Bitmap-m | | --- | --- | --- | --- | --- | 007 |James Bond |M |0 |1 008 |Amelie Lux |W |1 |0 010 |Harald Schmidt |M |0 |1 011 |Heike Drechsler |W |1 |0 - Selektion von Tupeln kann nun durch entsprechende Verknüpfung von Bitmap-Vektoren erfolgen - Beispiel: Bitmap-Index über Attribute Geschlecht und Geburtsmonat - (d.h. 2 Bitmap-Vektoren B-w und B-m für Geschlecht und 12 Bitmap-Vektoren B-1, ..., B-12 für die Monate, wenn alle Monate vorkommen) - Anfrage: "alle im März geborenen Frauen" - Berechnung: $B-w \wedge B-3$ (bitweise konjunktiv verknüpft) - Ergebnis: alle Tupel, an deren Position im Bitmap-Vektor des Ergebnis eine 1 steht Mehrkomponenten-Bitmap-Index - bei Standard-Bitmap-Indexe entstehen für Attribute mit vielen Ausprägungen sehr viele Bitmap-Vektoren - $$-Mehrkomponenten-Bitmap-Indexe erlauben $n*m$ mögliche Werte durch $n+m$ Bitmap-Vektoren zu indexieren - jeder Wert $x(0\leq x\leq n*m-1)$ kann durch zwei Werte $y$ und $z$ repräsentiert werden: $x=n*y+z$ mit $0\leq y\leq m-1$ und $0\leq z\leq n-1$ - dann nur noch maximal $m$ Bitmap-Vektoren für $y$ und $n$ Bitmap-Vektoren für $z$ - Speicheraufwand reduziert sich von $n*m$ auf $n+m$ - dafür müssen für eine Punktanfrage aber 2 Bitmap-Vektoren gelesen werden - Beispiel: Zweikomponenten-Bitmap-Index - Für $M= 0 ..11$ etwa $x= 4*y + z$ - y-Werte: $B-2-1, B-1-1, B-0-1$ - z-Werte: $B-3-0, B-2-0, B-1-0, B-0-0$ | x| y | || z ||||| | --- | --- | --- | --- | --- | --- | --- | --- | | M | B-2-1 | B-1-1 | B-0-1| B-3-0| B-2-0| B-1-0| B-0-0 |5| 0 |1 |0 |0 |0 |1 |0 |3| 0 |0 |1 |1 |0 |0 |0 |0| 0 |0 |1 |0 |0 |0 |1 |11| 1| 0 |0| 1 |0| 0 |0 Beispiel: Postleitzahlen - Kodierung von Postleitzahlen - Werte 00000 bis 99999 - direkte Umsetzung: 100.000 Spalten - Zweikomponenten-Bitmap-Index (erste 2 Ziffern + 3 letzte Ziffern): 1.100 Spalten - Fünf Komponenten: 50 Spalten - geeignet für Bereichsanfragen "PLZ 39***" - Binärkodiert (bis $2^17$): 34 Spalten - nur für Punktanfragen! - Bemerkung: Kodierung zur Basis 3 ergibt sogar nur 33 Spalten... ## Indexierung von Texten Indexierung von Texten - bisher vorgestellte Verfahren unterstützen prinzipiell auch die Indexierung von Zeichenketten - Probleme bereitet folgendes: - unterschiedliche Längen der Zeichenketten als Suchschlüssel - bei Sätzen: Zugriff auf einzelne Wörter bevorzugt - Ähnlichkeiten u.a. über gemeinsame Präfixe und Editierabstand Digital- und Präfixbäume - B-Bäume: Problem bei zu indexierenden Zeichenketten - Lösung: Digital- oder Präfixbäume - Digitalbäume indexieren (fest) die Buchstaben des zugrundeliegenden Alphabets - können dafür unausgeglichen werden - Beispiele: Tries, Patricia-Bäume - Präfixbäume indexieren Präfix der Zeichenketten Tries - von "Information Retrieval", aber wie try gesprochen - Abgrenzung vom _tree_ für allgemeine Suchbäume ![](Assets/DBimpl-tries.png) - Knoten eines Tries ![](Assets/Dbimpl-tries-knoten.png) - Probleme: lange gemeinsame Teilworte, nicht vorhandenen Buchstaben und Buchstabenkombinationen, möglicherweise leere Knoten, sehr unausgeglichene Bäume Patricia-Bäume - Tries: Probleme bei Teilekennzahlen, Pfadnamen, URLs (lange gemeinsame Teilworte) - Lösung: Practical Algorithm To Retrieve Information Coded In Alphanumeric (Patricia) - Prinzip: Überspringen von Teilworten - Problem: Datenbanksprache bei Suchbegriff _Triebwerksperre_ Patricia-Baum und Trie im Vergleich ![](Assets/Dbimpl-trie-vs-patricia.png) - übersprungene Teilworte zusätzlich speichern: Präfix-Bäume Präfix-Bäume - Patricia-Baum plus Abspeicherung der übersprungenen Präfixe ![](Assets/DBimpl-präfix-baum.png) Invertierte Listen - indizierte Worte (Zeichenketten) bilden eine lexikographisch sortierte Liste - einzelner Eintrag besteht aus einem _Wort_ und einer Liste von Dokument-Identifikatoren derjenigen Dokumente, in denen das Wort vorkommt - zusätzlich können weitere Informationen für die Wort-Dokument-Kombination abgespeichert werden: - Position des (ersten Auftretens des) Wortes im Text - Häufigkeit des Wortes im Text Invertierte Listen ![](Assets/DBimpl-invertierte-liste.png) ## Mehrdimensionale Speichertechniken Mehrdimensionale Speichertechniken - bisher: eindimensional (keine partial-match-Anfragen, nur lineare Ordnung) - jetzt: mehrdimensional (auch partial-match-Anfragen, Positionierung im mehrdimensionalen Datenraum) - $k$ Dimensionen = $k$ Attribute können gleichberechtigt unterstützt werden - dieser Abschnitt - mehrdimensionaler B-Baum - mehrdimensionales Hashverfahren ## Mehrdimensionale Baumverfahren Mehrdimensionale Baumverfahren - KdB-Baum ist B+-Baum, bei dem Indexseiten als binäre Bäume mit Zugriffsattributen, Zugriffsattributwerten und Zeigern realisiert werden - Varianten von $k$ -dimensionalen Indexbäumen: - _kd-Baum_ von Bentley und Friedman: für Hauptspeicheralgorithmen entwickelte, mehrdimensionale Grundstruktur (binärer Baum) - _KDB-Baum_ von Robinson: Kombination kd-Baum und B-Baum ( $k$ -dimensionaler Indexbaum bekommt höheren Verzweigungsgrad) - _KdB-Baum_ von Kuchen: Verbesserung des Robinson-Vorschlags, wird hier behandelt - KdB-Baum kann Primär- und mehrere Sekundärschlüssel gleichzeitig unterstützen - macht als Dateiorganisationsform zusätzliche Sekundärindexe überflüssig Definition KdB-Baum - Idee: auf jeder Indexseite einen Teilbaum darstellen, der nach mehreren Attributen hintereinander verzweigt - _KdB-Baum vom Typ_ ( $b$ , $t$ ) besteht aus - inneren Knoten (Bereichsseiten) die einen _kd-Baum_ mit maximal $b$ internen Knoten enthalten - Blättern (Satzseiten) die bis zu $t$ Tupel der gespeicherten Relation speichern können - Bereichsseiten: _kd-Baum_ enthalten mit Schnittelementen und zwei Zeigern - Schnittelement enthält Zugriffsattribut und Zugriffsattributwert; - linker Zeiger: kleinere Zugriffsattributwerte; - rechter Zeiger: größere Zugriffsattributwerte Beispiel ![](Assets/Dbimpl-kdb-baum-beispiel.png) KdB-Baum: Struktur - Bereichsseiten - Anzahl der Schnitt- und Adressenelemente der Seite - Zeiger auf Wurzel des in der Seite enthaltenen kd-Baumes - Schnitt- und Adressenelemente. - Schnittelement - Zugriffsattribut - Zugriffsattributwert - zwei Zeiger auf Nachfolgerknoten des kd-Baumes dieser Seite (können Schnitt- oder Adressenelemente sein) - Adressenelemente: Adresse eines Nachfolgers der Bereichsseite im KdB-Baum (Bereichs- oder Satzseite) KdB-Baum: Operationen - Komplexität $lookup$, $insert$ und $delete$ bei exact-match $O(log n)$ - bei partial-match besser als $O(n)$ - bei $t$ von $k$ Attributen in der Anfrage spezifiziert: Zugriffskomplexität von $O(n^{1-t/k})$ KdB-Baum: Trennattribute - Reihenfolge der Trennattribute - entweder zyklisch festgelegt - oder Selektivitäten einbeziehen: Zugriffsattribut mit hoher Selektivität sollte früher und häufiger als Schnittelement eingesetzt werden - Trennattributwert: aufgrund von Informationen über Verteilung von Attributwerten eine geeignete "Mitte" eines aufzutrennenden Attributwertebereichs ermitteln KdB-Baum: Brickwall ![](Assets/DBimpl-kdb-baum-brickwall.png) ## Mehrdimensionales Hashen Mehrdimensionales Hashen - Idee: Bit Interleaving - abwechselnd von verschiedenen Zugriffsattributwerten die Bits der Adresse berechnen - Beispiel: zwei Dimensionen | | *0*0 | *0*1 | *1*0| *1*1| | --- | --- | --- | --- | --- | 0*0*| 0000 |0001 |0100 |0101 0*1*| 0010 |0011 |0110 |0111 1*0*| 1000 |1001 |1100 |1101 1*1*| 1010 |1011 |1110 |1111 MDH von Kuchen - MDH baut auf linearem Hashen auf - Hash-Werte sind Bitfolgen, von denen jeweils ein Anfangsstück als aktueller Hashwert dient - je ein Bitstring pro beteiligtem Attribut berechnen - Anfangsstücke nun nach dem Prinzip des Bit-Interleaving zyklisch abarbeiten - Hashwert reihum aus den Bits der Einzelwerte zusammensetzen MDH formal - mehrdimensionaler Wert $x := (x_1,..., x_k)\in D = D_1\times ...\times D_k$ - Folge von mit _i_ indizierten Hashfunktionen konstruieren - _i_-te Hashfunktion $h_i(x)$ wird mittels Kompositionsfunktion $\bar{h}_i$ aus den jeweiligen $i$ -ten Anfangsstücken der lokalen Hash-Werte $h_{i_j}(x_j)$ zusammengesetzt: $h_i(x)=\bar{h}_i(h_{i_1}(x_1),...,h_{i_k}(x_k))$ - lokale Hashfunktionen $h_{i_j}$ ergeben jeweils Bitvektor der Länge $z_{i_j} +1$:$h_{i_j} : D_j\rightarrow \{0,..., z_{i_j}\}, j\in\{1 ,..., k\}$ - $z_{i_j}$ sollten möglichst gleich groß sein, um die Dimensionen gleichmäßig zu berücksichtigen - Kompositionsfunktion $\bar{h}_i$ setzt lokale Bitvektoren zu einem Bitvektor der Länge $i$ zusammen: $\bar{h}_i:\{0,...,z_{i1}\times ...\times\{0,..., z_{i_k}\}\rightarrow\{0,...,2^{i+1}-1\}$ - ausgeglichene Länge der $z_{i_j}$ wird durch folgende Festlegung bestimmt, die Längen zyklisch bei jedem Erweiterungsschritt an einer Stelle erhöht: $$z_{i_j} = \begin{cases} 2^{\lfloor \frac{i}{k}\rfloor +1} -1 \quad\text{ für } j-1\leq (i mod k)\\ 2^{\lfloor \frac{i}{k}\rfloor} -1 \quad\text{ für } j-1 > (i mod k) \end{cases}$$ - Kompositionsfunktion: $$\bar{h}_i(x)=\sum_{r=0}^i (\frac{(x_{(r mod k)+1} mod 2^{\lfloor \frac{r}{k}\rfloor +1}) - (x_{(r mod k)+1} mod 2^{\lfloor \frac{r}{k}\rfloor})}{2^{\lfloor \frac{r}{k}\rfloor})}) 2^r$$ MDH Veranschaulichung ![](Assets/DBimpl-mdh-veranschaulichung.png) - verdeutlicht Komposition der Hashfunktion $h_i$ für drei Dimensionen und den Wert $i=7$ - graphisch unterlegte Teile der Bitstrings entsprechen den Werten $h_{7_1}(x_1),h_{7_2}(x_2)$ und $h_{7_3}(x_3)$ - beim Schritt auf $i=8$ würde ein weiteres Bit von $x_2$ (genauer: von $h_{8_2}(x_2)$) verwendet MDH Komplexität - Exact-Match-Anfragen: $O(1)$ - Partial-Match-Anfragen, bei $t$ von $k$ Attributen festgelegt, Aufwand $O(n^{1-\frac{t}{k}})$ - ergibt sich aus der Zahl der Seiten, wenn bestimmte Bits "unknown" - Spezialfälle: $O(1)$ für $t=k$, $O(n)$ für $t=0$ ## Geometrische Zugriffsstrukturen Geometrische Zugriffsstrukturen - große Mengen geometrischer Objekte ($> 10^6$) - Eigenschaften geometrischer Objekte - Geometrie (etwa Polygonzug) - zur Unterstützung bei Anfragen: zusätzlich $d$-dimensionales umschreibendes Rechteck (bounding box) - nichtgeometrische Attribute - Anwendungsszenarien: Geoinformationssysteme (Katasterdaten, Karten), CAx-Anwendungen (etwa VLSI Entwurf), ... - Zugriff primär über Geometriedaten: Suchfenster (Bildschirmausschnitt), Zugriff auf benachbarte Objekte Typische Operationen - exakte Suche - Vorgabe: exakte geometrische Suchdaten - Ergebnis: maximal ein Objekt - Bereichsanfrage für vorgegebenes $n$-dimensionales Fenster - Suchfenster: $d$-dimensionales Rechteck (entspricht mehrdimensionalem Intervall) - Ergebnis sind alle geometrischen Objekte, die das Suchfenster schneiden - Ergebnisgröße parameterabhängig - Einfügen von geometrischen Objekten - wünschenswert ohne globale Reorganisation! - Löschen von geometrischen Objekten - wünschenswert ohne globale Reorganisation! Nachbarschaftserhaltende Suchbäume - Aufteilung des geometrischen Bereichs in Regionen - benachbarte Objekte wenn möglich der selben Region zuordnen - falls dieses nicht geht, diese auf benachbarte Regionen aufteilen - Baumstruktur entsteht durch Verfeinerung von Regionen in benachbarte Teilregionen - Speicherung von Objekten erfolgt in den Blattregionen - Freiheitsgrade - Form der Regionen - vollständige Partitionierung oder Überlappung durch die Regionen - eindeutige Zuordnung von Objekten zu Regionen oder Mehrfachzuordnung - Speicherung und Zugriff über Originalgeometrie oder über abgeleitete Geometrie für Objekte - Grad des Baumes & Organisationsform Mehrstufige Bearbeitung geom. Anfragen ![](Assets/DBimpl-mehrstufige-geom-bearbeitung.png) Geom. Baumstruktur: BSP-Baum - Binary Space Partitioning: schrittweises binäres Teilen des Datenraums ![](Assets/DBimpl-bsp-partition.png) Realisierungsvarianten | Alternative | Baumstrukturen | | | | --- | --- | --- | --- | | | BSP-Baum | R-Baum | R+-Baum | Regionenform | konvexe Polygone |Rechtecke| Rechtecke| Teilregionen |vollständig |unvollständig |unvollständig| Überlappung |nein |ja |nein| ausgeglichen |nein |ja |ja| R-Bäume - R-Baum: Verallgemeinerung des B-Baum-Prinzips auf mehrere Dimensionen - Baumwurzel entspricht einem Rechteck, das alle geometrischen Objekte umfasst - Geo-Objekte werden durch ihre umschließenden Rechtecke repräsentiert - Aufteilung in Regionen erfolgt in nichtdisjunkte Rechtecke - Jedes Geo-Objekt ist eindeutig einem Blatt zugeordnet - Regionenaufteilung durch Rechtecke im R-Baum ![](Assets/DBimpl-r-baum.png) - Baumstruktur für R-Baum ![](Assets/DBimpl-r-baum-struktur.png) Probleme mit R-Bäumen - gegebenes Rechteck kann von vielen Regionen überlappt werden, es ist aber genau in einer Region gespeichert - auch Punktanfragen können eine Suche in sehr vielen Rechteckregionen bedeuten - Ineffizient bei exakter Suche (exakte Suche auch bei Einfügen und Löschen notwendig!) - Probleme beim Einfügen - Einfügen erfordert oft Vergrößern von Regionen (aufwärts propagiert) Vergrößern beim Einfügen ![](Assets/DBimpl-r-baum-vergrößern.png) R+-Bäume - R+-Bäume: Aufteilung in Teilregionen disjunkt - Jedem gespeicherten Punkt des geometrischen Bereichs ist eindeutig ein Blatt zugeordnet - In jeder Baumebene ist einem Punkt ebenfalls maximal ein Rechteck zugeordnet -> eindeutiger Pfad von der Wurzel zum speichernden Blatt - ‘Clipping’ von Geo-Objekten notwendig! ![](Assets/DBimpl-r+-baum.png) ![](Assets/DBimpl-r+-baum-struktur.png) Probleme mit R+-Bäumen - Objekte müssen in mehreren Rechteckregionen gespeichert werden (clipping) - erhöhter Speicher- & Modifikationsaufwand - Einfügen von Objekten erfordert möglicherweise Modifikation mehrerer Rechteckregionen ![](Assets/DBimpl-r+-baum-problem.png) - Einfügen kann in bestimmten Situationen unvermeidbar zu Regionenaufteilungen führen ![](Assets/Dbimpl-r+-baum-problem-2.png) - Regionenmodifikationen haben Konsequenzen sowohl in Richtung Blätter als auch in Richtung Wurzel - obere Grenze für Einträge in Blattknoten kann nicht mehr garantiert werden Rechteckspeicherung durch Punktdatenstrukturen - Probleme bei ausgedehnten Geometrien -> Entartung - Nutzung "robuster" mehrdimensionaler Indexstrukturen (z.B. Grid-File) durch Abbildung von Rechtecken auf Punkte möglich? Punktdatenstrukturen - Rechteckspeicherung durch Punktdatenstrukturen - Transformation von ausgedehnten Objekten (mehrdimensionale Rechtecke) in Punktdaten - Transformation bildet $d$-dimensionale Rechtecke auf Punkte im $2d$-dimensionalen Raum $R^{2d}$ ab - $d$-dimensionale Rechteck: $r=[l_1, r_1]\times ...\times [l_d, r_d]$ - Eckentransformation $p_r = (l_1, r_1,..., l_d, r_d) \in R^{2d}$ pro Intervall als Koordinaten: obere Schranke, untere Schranke - Mittentransformation $p_r =(\frac{l_1+r_1}{2}, \frac{r_1-l_1}{2},...,\frac{l_d+l_r}{2},\frac{r_d-l_d}{2})\in\mathbb{R}^{2d}$ pro Intervall als Koordinaten: Mittelpunkt, halbe Breite Eckentransformation ![](Assets/DBimpl-eckentransformation.png) Mittentransformation ![](Assets/DBimpl-mittentransformation.png) Suchfenster ![](Assets/DBimpl-suchfenster.png) Grid-File-Degeneration ![](Assets/Dbimpl-grid-file-degeneration.png) ## Approximierende Verfahren Approximierende Verfahren - insbesondere für hochdimensionale Daten - pro Tupel einen approximierenden Bit-Code mit nur wenigen Bits pro Dimensionen - beispielsweise ein Grid mit jeweils $2^k$ Bit pro Dimension - ordnungserhaltend wenn die Bits geeignet gewählt werden - adressiert werden die Zellen in denen ein Punkt liegt - Nutzung zur Nachbarschaftssuche in hochdimensionalen Räumen - VA-File von Weber et al. - ersetzt Durchlauf über alle Punkte durch Durchlauf über alle Approximationswerte zur Vorauswahl von Kandidaten VA-File ![](Assets/DBimpl-va-file.png) Zusammenfassung - Bitmap-Indexe - Digital- und Präfixbäume - Mehrdimensionale Verfahren - Geometrische Zugriffsstrukturen