
Automatisiertes Testen zur Erkennung von Logik- und Performance-Fehlern in Datenbanksystemen

SQLancer ist ein Werkzeug, um Datenbankmanagementsysteme (DBMS) automatisch zu testen, um Fehler in deren Implementierung zu finden. Das heißt, es findet Fehler im Code der DBMS-Implementierung und nicht in vom Benutzer geschriebenen Abfragen. SQLancer hat Hunderte von Fehlern in ausgereiften und weit verbreiteten DBMS gefunden.
SQLancer bewältigt zwei wesentliche Herausforderungen beim automatischen Testen von DBMS:
Community. Wir haben einen Slack-Arbeitsbereich zur Diskussion über SQLancer und DBMS-Testing im Allgemeinen. Früher hatte SQLancer einen Account auf Twitter/X @sqlancer_dbms, der nicht mehr gepflegt wird. Wir haben einen Blog, der derzeit nur Beiträge von Mitwirkenden des Google Summer of Code-Projekts enthält.
Mindestanforderungen:
Die folgenden Befehle klonen SQLancer, erstellen ein JAR und starten SQLancer zum Testen von SQLite mit Non-optimizing Reference Engine Construction (NoREC):``` git clone https://github.com/sqlancer/sqlancer cd sqlancer mvn package -DskipTests cd target java -jar sqlancer-*.jar --num-threads 4 sqlite3 --oracle NoREC
**Ausführung und Beendigung.** Wenn die Ausführung alle fünf Sekunden Fortschrittsinformationen ausgibt, funktioniert das Tool wie erwartet. Mit der Tastenkombination STRG+C kann SQLancer manuell beendet werden. Wenn SQLancer keine Fehler findet, wird es unbegrenzt ausgeführt. Die Option `--num-tries` kann verwendet werden, um festzulegen, nach wie vielen Fehlern SQLancer beendet wird. Alternativ kann die Option `--timeout-seconds` verwendet werden, um die maximale Laufzeit von SQLancer anzugeben.
**Parameter.** Wenn Sie SQLancer ohne Parameter starten, werden verfügbare Optionen und Befehle angezeigt. Beachten Sie, dass allgemeine Optionen, die von allen DBMS-Testimplementierungen unterstützt werden (z. B. `--num-threads`), vor dem Namen des zu testenden DBMS (z. B. `sqlite3`) stehen müssen. Optionen, die nur für bestimmte DBMS unterstützt werden (z. B. `--test-rtree` für SQLite3), oder Optionen, für die jede Testimplementierung unterschiedliche Werte bereitstellt (z. B. `--oracle NoREC`), müssen nach dem DBMS-Namen stehen.
**DBMS.** Um SQLancer auf SQLite auszuführen, war es nicht erforderlich, ein DBMS zu installieren und einzurichten. Der Grund dafür ist, dass eingebettete DBMS im selben Prozess wie die Anwendung ausgeführt werden und daher keine separate Installation oder Einrichtung erfordern. Von SQLancer unterstützte eingebettete DBMS umfassen DuckDB, H2 und SQLite. Ihre Binärdateien sind als [JAR-Abhängigkeiten](https://github.com/sqlancer/sqlancer/blob/main/pom.xml) enthalten. Beachten Sie, dass Abstürze in diesen Systemen auch zu einem Absturz der JVM führen, auf der SQLancer ausgeführt wird.
# Verwendung von SQLancer
**Protokolle.** SQLancer speichert Protokolle im Unterverzeichnis `target/logs`. Standardmäßig ist die Option `--log-each-select` aktiviert, was dazu führt, dass jede an das DBMS gesendete SQL-Anweisung protokolliert wird. Die entsprechenden Dateinamen haben das Suffix `-cur.log`. Darüber hinaus erstellt SQLancer bei Erkennung eines Logikfehlers eine Datei mit der Erweiterung `.log`, in der die Anweisungen zur Reproduktion des Fehlers protokolliert werden, einschließlich nur der zuletzt ausgeführten Abfrage zusammen mit den anderen Anweisungen zum Einrichten des Datenbankzustands.
**Reduzieren von Fehlern.** Nachdem eine fehlerauslösende Testeingabe gefunden wurde, muss die Eingabe in der Regel reduziert werden, um sie weiter zu analysieren, da sie viele SQL-Anweisungen enthalten kann, die für die Reproduktion des Fehlers redundant sind. Eine Möglichkeit besteht darin, dies manuell zu tun, indem nach und nach eine Anweisung oder ein Feature entfernt wird, die fehlerauslösenden Anweisungen wiederholt abgespielt werden und das Testorakel angewendet wird (z. B. würde dies bei Testorakeln wie TLP oder NoREC erfordern, dass überprüft wird, ob beide Abfragen weiterhin ein unterschiedliches Ergebnis liefern). Dieser Prozess kann mit einem sogenannten [Delta-Debugging-Ansatz](https://www.debuggingbook.org/html/DeltaDebugger.html) automatisiert werden. SQLancer enthält eine experimentelle Implementierung eines Delta-Debugging-Ansatzes, der mit `--use-reducer` aktiviert werden kann. In der Vergangenheit haben wir erfolgreich [C-Reduce](https://embed.cs.utah.edu/creduce/) verwendet, was die Angabe des Testorakels in einem Skript erfordert, das von C-Reduce ausgeführt werden kann.
**Testen der neuesten DBMS-Version.** Für die meisten DBMS unterstützt SQLancer nur eine frühere *Release*-Version. Daher könnten potenzielle Fehler, die SQLancer findet, in der neuesten *Entwicklungsversion* des DBMS bereits behoben sein. Wenn Sie kein Entwickler des von Ihnen getesteten DBMS sind, möchten wir Sie ermutigen, zu überprüfen, ob der Fehler noch reproduziert werden kann, bevor Sie ihn melden. Wir würden es begrüßen, wenn Sie SQLancer erwähnen, wenn Sie von ihm gefundene Fehler melden. Wir würden uns auch freuen, von Ihren Erfahrungen mit der Verwendung von SQLancer oder verwandten Anwendungsfällen oder Erweiterungen zu hören.
**Optionen.** SQLancer bietet viele Optionen, mit denen Sie sein Verhalten anpassen können. Die Ausführung von `java -jar sqlancer-*.jar --help` listet diese auf und sollte eine Ausgabe wie die folgende ausgeben:```
Usage: SQLancer [options] [command] [command options]
Options:
--ast-reducer-max-steps
EXPERIMENTAL Maximum steps the AST-based reducer will do
Default: -1
--ast-reducer-max-time
EXPERIMENTAL Maximum time duration (secs) the statement reducer will do
Default: -1
--canonicalize-sql-strings
Should canonicalize query string (add ';' at the end
Default: true
--constant-cache-size
Specifies the size of the constant cache. This option only takes effect
when constant caching is enabled
Default: 100
...
Welche SQLancer-Version verwenden. Die empfohlene Methode zur Verwendung von SQLancer ist die Verwendung der neuesten Quellversion auf GitHub. Unregelmäßige und seltene offizielle Veröffentlichungen sind auch auf den folgenden Plattformen verfügbar:
Verständnis der SQL-Generierung. Um fehlerverursachende Anweisungen zu analysieren, ist es hilfreich, die Eigenschaften von SQLancer zu verstehen. Erstens wird erwartet, dass SQLancer immer SQL-Anweisungen generiert, die für das getestete DBMS syntaktisch gültig sind. Daher sollten Sie niemals Syntaxfehler beobachten. Zweitens könnte SQLancer Anweisungen generieren, die semantisch ungültig sind. Beispielsweise könnte SQLancer versuchen, doppelte Werte in eine Spalte mit einer UNIQUE-Einschränkung einzufügen, da es schwierig ist, solche semantischen Fehler vollständig zu vermeiden. Drittens wird erwartet, dass jeder von SQLancer gemeldete Fehler ein echter Fehler ist, außer denen, die von CERT gemeldet werden (da Leistungsprobleme nicht so klar definiert sind wie andere Arten von Fehlern). Wenn Sie Fehler beobachten, die von SQLancer angezeigt werden und die Sie nicht als Fehler betrachten, ist wahrscheinlich etwas mit Ihrer Einrichtung falsch. Schließlich ist SQLancer, wie oben erwähnt, spezifisch für eine Version des DBMS, und Sie können die Version, gegen die wir testen, in unserem GitHub Actions-Workflow finden. Wenn Sie gegen eine andere Version testen, könnten Sie verschiedene Fehlalarme beobachten (z. B. verursacht durch Syntaxfehler). Obwohl wir möchten, dass SQLancer immer auf dem neuesten Stand der aktuellen Entwicklungsversion jedes DBMS ist, fehlen uns die Ressourcen, um dies zu erreichen.
Unterstützte DBMS. SQLancer benötigt DBMS-spezifischen Code für jedes DBMS, das es unterstützt. Stand Januar 2025 bietet es Unterstützung für Citus, ClickHouse, CockroachDB, Databend, (Apache) DataFusion, (Apache) Doris, DuckDB, H2, HSQLDB, MariaDB, Materialize, MySQL, OceanBase, PostgreSQL, Presto, QuestDB, SQLite3, TiDB und YugabyteDB. Das Ausmaß der Unterstützung der einzelnen DBMS unterscheidet sich.
SQLancer hat mehrere Ansätze für das Testen von DBMS entwickelt und implementiert, wie unten in chronologischer Reihenfolge aufgeführt.
Die .bib-Einträge finden Sie hier. |
Ich führe SQLancer auf der neuesten Version eines unterstützten DBMS aus. Ist es zu erwarten, dass SQLancer viele AssertionErrors ausgibt? In vielen Fällen unterstützt SQLancer nicht die neueste Version eines DBMS. Sie können die Datei .github/workflows/main.yml überprüfen, um festzustellen, welche Version wir in unseren CI-Tests verwenden. Dies entspricht der derzeit unterstützten Version dieses DBMS. SQLancer sollte nur einen AssertionError ausgeben und eine entsprechende Logdatei erstellen, wenn es einen Fehler identifiziert hat. Um SQLancer zu aktualisieren, um eine neue DBMS-Version zu unterstützen, sind zwei Optionen empfehlenswert: (1) Die Generatoren können aktualisiert werden, um bestimmte Muster nicht mehr zu generieren, die Fehler verursachen könnten (z. B. wenn ein Schlüsselwort oder eine Option nicht mehr unterstützt wird), oder (2) die neu auftretenden Fehler können als erwartete Fehler hinzugefügt werden, sodass SQLancer sie ignoriert, wenn sie auftreten (z. B. nützlich, wenn bestimmte fehlerverursachende Muster nicht einfach vermieden werden können).
Ein weiterer Grund für viele Fehler bei einer unterstützten Version könnte sein, dass Fehlermeldungen in einem nicht-englischen Gebietsschema ausgegeben werden (was dann im Stacktrace sichtbar wäre). Versuchen Sie in einem solchen Fall, das Gebietsschema des DBMS auf Englisch zu setzen (z. B. siehe die PostgreSQL-Startseite).
Wenn ich SQLancer starte, erhalte ich einen Fehler wie "database 'test' does not exist". Wie kann ich SQLancer ohne diesen Fehler ausführen? Für einige DBMS erwartet SQLancer, dass eine Datenbank "test" existiert, die es dann als Anfangsdatenbank zum Verbinden verwendet. Wenn Sie eine solche Datenbank noch nicht erstellt haben, können Sie mit einem Befehl wie CREATE DATABASE test diese Datenbank erstellen (z. B. siehe die PostgreSQL-Dokumentation).
Dokumentation und Ressourcen:
Videos:
Eng verwandte Werkzeuge:
| Technik | Veranstaltungsort | Links | Beschreibung |
|---|
| Pivoted Query Synthesis (PQS) | OSDI 2020 | Paper Video | PQS ist die erste Technik, die wir entworfen und implementiert haben. Sie wählt zufällig eine Zeile, genannt Pivot-Zeile, für die eine Abfrage generiert wird, die garantiert diese Zeile abruft. Wenn die Zeile nicht im Ergebnissatz enthalten ist, wurde ein Fehler erkannt. Sie ist hier vollständig beschrieben. PQS erkennt Fehler effektiv, erfordert aber mehr Implementierungsaufwand als andere Testansätze, die einer metamorphischen oder differentiellen Testmethodik folgen. Daher wird sie derzeit nicht mehr gewartet. |
| Non-optimizing Reference Engine Construction (NoREC) | ESEC/FSE 2020 | Paper Video | NoREC zielt darauf ab, Optimierungsfehler zu finden. Es übersetzt eine Abfrage, die möglicherweise vom DBMS optimiert wird, in eine, auf die kaum Optimierungen anwendbar sind, und vergleicht die beiden Ergebnismengen. Eine Abweichung zwischen den Ergebnismengen weist auf einen Fehler im DBMS hin. Der Ansatz gilt hauptsächlich für einfache Abfragen mit einem Filterprädikat. |
| Ternary Logic Partitioning (TLP) | OOPSLA 2020 | Paper Video | TLP partitioniert eine Abfrage in drei Partitionierungsabfragen, deren Ergebnisse zusammengesetzt und mit dem Ergebnissatz der ursprünglichen Abfrage verglichen werden. Eine Abweichung in den Ergebnismengen weist auf einen Fehler im DBMS hin. Im Gegensatz zu NoREC und PQS kann es Fehler in erweiterten Funktionen wie Aggregatfunktionen erkennen. Es gehört zu den am weitesten verbreiteten Testtechniken. |
| Differential Query Execution (DQE) | ICSE 2023 | Paper Code | Differential Query Execution (DQE) ist ein neuartiger und allgemeiner Ansatz zur Erkennung von Logikfehlern in SELECT-, UPDATE- und DELETE-Abfragen. DQE löst das Test-Orakel-Problem, indem SELECT-, UPDATE- und DELETE-Abfragen mit demselben Prädikat φ ausgeführt und Inkonsistenzen zwischen ihren Ausführungsergebnissen beobachtet werden. Wenn z. B. eine Zeile, die von einer UPDATE-Abfrage mit einem Prädikat φ aktualisiert wird, nicht im Abfrageergebnis einer SELECT-Abfrage mit demselben Prädikat φ erscheint, wird ein Logikfehler im Ziel-DBMS erkannt. Wir fügen jeder Tabelle in einer Datenbank zwei zusätzliche Spalten hinzu, um jede Zeile eindeutig zu identifizieren und nachzuverfolgen, ob eine Zeile geändert wurde. Wir schreiben SELECT- und UPDATE-Abfragen weiter um, um ihre zugegriffenen Zeilen zu identifizieren. DQE unterstützt MySQL. |
| Query Plan Guidance (QPG) | ICSE 2023 | Paper Video Code | QPG ist ein feedbackgesteuerter Ansatz zur Generierung von Testfällen. Es basiert auf der Erkenntnis, dass Abfragepläne erfassen, ob interessantes Verhalten im DBMS ausgeführt wird. Es funktioniert, indem der Datenbankzustand mutiert wird, wenn nach der Ausführung einer Anzahl von Abfragen keine neuen Abfragepläne beobachtet wurden, in der Erwartung, dass der neue Zustand das Auslösen neuer Abfragepläne ermöglicht. Dieser Ansatz wird mit der Option --qpg-enable aktiviert und unterstützt TLP- und NoREC-Orakel für SQLite, CockroachDB, TiDB und Materialize. Es ist der einzige Ansatz, der speziell das Problem der Testeingabegenerierung angeht. |
| Cardinality Estimation Restriction Testing (CERT) | ICSE 2024 | Paper Code | CERT zielt darauf ab, Leistungsprobleme durch unerwartete geschätzte Kardinalitäten zu finden, die die geschätzte Anzahl zurückgegebener Zeilen darstellen. Aus einer gegebenen Eingabeabfrage leitet es eine restriktivere Abfrage ab, deren geschätzte Kardinalität nicht größer sein sollte als die der ursprünglichen Abfrage. Eine Verletzung weist auf ein potenzielles Leistungsproblem hin. CERT unterstützt TiDB, CockroachDB und MySQL. CERT ist das einzige Test-Orakel, das Teil von SQLancer ist und entwickelt wurde, um Leistungsprobleme zu finden. |
| Differential Query Plans (DQP) | SIGMOD 2024 | Paper Video Code | DQP zielt darauf ab, Logikfehler zu finden, indem die Ausführung verschiedener Abfragepläne für eine bestimmte Abfrage gesteuert und überprüft wird, ob sie ein konsistentes Ergebnis liefern. DQP unterstützt MySQL, MariaDB und TiDB. |
| Constant Optimization Driven Database System Testing (CODDTest) | SIGMOD 2025 | Code | CODDTest findet Logikfehler in DBMS, auch in erweiterten Funktionen wie Unterabfragen. Es basiert auf der Erkenntnis, dass wir den Datenbankzustand für eine Datenbanksitzung als konstant annehmen können, was es uns dann ermöglicht, Teile einer Abfrage durch ihre Ergebnisse zu ersetzen, was im Wesentlichen Konstantenfaltung und Konstantenweitergabe entspricht, zwei traditionelle Compileroptimierungen. |