Schema
Typen
Es ist wichtig, den richtigen Typ zur Kodierung von Spalten zu verwenden (z. B. BIGINT, DATE, DATETIME). Zwar lassen sich spezifischere Werte immer mit Zeichenkettentypen (VARCHAR usw.) kodieren, empfohlen ist das aber nicht. Zeichenketten brauchen mehr Platz und sind bei Operationen wie Filterung, Join und Aggregation langsamer. Beim Laden von CSV-Dateien können Sie den Auto-Erkennungsmechanismus des CSV-Readers nutzen, um die richtigen Typen für CSV-Eingaben zu bestimmen.
In speicherarmen Umgebungen können kleinere Datentypen (z. B. TINYINT) den Speicher- und Festplattenbedarf einer Abfrage senken. DuckDBs Bitpacking-Kompression bedeutet, dass kleine Werte in größeren Datentypen auf der Festplatte nicht mehr Platz belegen, während der Verarbeitung aber mehr Speicher brauchen – daher ist es nötig, beim Anlegen von Spalten möglichst restriktive Typen zu verwenden, um den Speicherverbrauch zu senken.
Mikrobenchmark: Zeitstempel verwenden
Wir veranschaulichen den Unterschied in der Aggregationsgeschwindigkeit anhand der Spalte creationDate der LDBC-Comment-Tabelle bei Scale Factor 300. Diese Tabelle hat ca. 554 Millionen ungeordnete Zeitstempelwerte. Wir führen eine einfache Aggregationsabfrage aus, die den durchschnittlichen Tag im Monat aus den Zeitstempeln in zwei Konfigurationen zurückgibt.
Zuerst kodieren wir die Werte als DATETIME und führen die Abfrage mit der Datetime-Funktion extract aus:
SELECT avg(extract('day' FROM creationDate)) FROM Comment;Als Zweites verwenden wir den Typ VARCHAR und Zeichenkettenoperationen:
SELECT avg(CAST(creationDate[9:10] AS INTEGER)) FROM Comment;Die Ergebnisse des Mikrobenchmarks sind wie folgt:
| Spaltentyp | Speichergröße | Abfragezeit |
|---|---|---|
DATETIME |
3.3 GB | 0.9 s |
VARCHAR |
5.2 GB | 3.9 s |
Die Ergebnisse zeigen, dass der Wert DATETIME kleinere Speichergrößen und schnellere Verarbeitung ergibt.
Mikrobenchmark: Joins über Zeichenketten
Wir veranschaulichen den Unterschied durch Joins über verschiedene Typen anhand eines Self-Joins auf der LDBC-Comment-Tabelle bei Scale Factor 100. Die Tabelle hat 64-Bit-Integer-Identifikatoren, die als Attribut id jeder Zeile verwendet werden. Wir führen die folgende Join-Operation aus:
SELECT count(*) AS countFROM Comment c1JOIN Comment c2 ON c1.ParentCommentId = c2.id;Im ersten Experiment verwenden wir die richtigen (restriktivsten) Typen, d. h. sowohl die Spalte id als auch ParentCommentId sind als BIGINT definiert.
Im zweiten Experiment definieren wir alle Spalten mit dem Typ VARCHAR.
Die Ergebnisse der Abfragen sind in beiden Experimenten gleich, ihre Laufzeiten unterscheiden sich jedoch deutlich.
Die folgenden Ergebnisse zeigen, dass ein Join über BIGINT-Spalten etwa 1.8× schneller ist als derselbe Join über VARCHAR-typisierte Spalten, die denselben Wert kodieren.
| Join-Spalte (Nutzlasttyp) | Join-Spalte (Schematyp) | Beispielwert | Abfragezeit |
|---|---|---|---|
BIGINT |
BIGINT |
70368755640078 |
1.2 s |
BIGINT |
VARCHAR |
'70368755640078' |
2.1 s |
Bestpractice Vermeiden Sie es, numerische Werte als Zeichenketten darzustellen, besonders wenn Sie z. B. Join-Operationen darauf ausführen wollen.
Constraints
DuckDB erlaubt die Definition von Constraints wie UNIQUE, PRIMARY KEY und FOREIGN KEY. Diese Constraints können die Datenintegrität sichern, wirken sich aber negativ auf die Ladeleistung aus, weil Indizes aufgebaut und Prüfungen durchgeführt werden müssen. Außerdem verbessern sie die Abfrageleistung nur sehr selten, da DuckDB für Join- und Aggregationsoperatoren nicht auf diese Indizes angewiesen ist (siehe Indizierung für weitere Details).
Bestpractice Definieren Sie keine Constraints, außer Ihr Ziel ist die Sicherung der Datenintegrität.
Mikrobenchmark: Der Einfluss von Primärschlüsseln
Wir veranschaulichen den Einfluss von Primärschlüsseln anhand der LDBC-Comment-Tabelle bei Scale Factor 300.
Diese Tabelle hat ca. 554 Millionen Einträge.
Im ersten Experiment legen wir das Schema mit Primärschlüssel an und laden dann die Daten.
Im zweiten Experiment legen wir das Schema ohne Primärschlüssel an und laden dann die Daten.
Im dritten Experiment legen wir das Schema ohne Primärschlüssel an, laden die Daten und fügen anschließend den Primärschlüssel-Constraint hinzu.
In allen Fällen stammen die Daten aus .csv.gz-Dateien; wir messen die für das Laden benötigte Zeit.
| Vorgang | Ausführungszeit |
|---|---|
| Laden mit Primärschlüssel | 461.6 s |
| Laden ohne Primärschlüssel | 121.0 s |
| Laden ohne Primärschlüssel, danach Primärschlüssel setzen | 242.0 s |
Für diesen Datensatz haben Primärschlüssel nur einen (kleinen) positiven Effekt auf hochselektive Abfragen, etwa beim Filtern auf einen einzelnen Identifikator. Das Definieren von Primärschlüsseln (oder Indizes) hat keinen Einfluss auf Join- und Aggregationsoperatoren.
Bestpractice Für beste Bulk-Ladeleistung vermeiden Sie Primärschlüssel-Constraints. Falls sie nötig sind, definieren Sie sie nach dem Bulk-Laden.