worauf erfahrene Reviewer wirklich achten
Ein SQL-Review, das nur auf Einrueckung und Variablennamen schaut, uebersieht die Fehler, die spaeter in Produktion richtig teuer werden. Eine systematische Checkliste aus Lesbarkeit, Korrektheit, Performance-Red-Flags und Sicherheitspruefungen macht SQL-Code-Reviews reproduzierbar statt vom Bauchgefuehl des Reviewers abhaengig.
Inhaltsverzeichnis
- 1. Warum SQL-Code-Reviews eigene Regeln brauchen
- 2. Lesbarkeit und Formatierung als Basis jeder Review
- 3. Korrektheit: NULL-Handling, Joins und Aggregation pruefen
- 4. Performance-Red-Flags im Review erkennen
- 5. Sicherheit: Injection und Rechtevergabe im Review pruefen
- 6. Transaktionsgrenzen und Nebenlaeufigkeit bewerten
- 7. Migrations- und Schema-Aenderungen im Review absichern
- 8. Die Checkliste als wiederverwendbares Werkzeug etablieren
- 9. Automatisierte Linter versus manuelle Review im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum SQL-Code-Reviews eigene Regeln brauchen
Ein SQL-Code-Review unterscheidet sich fundamental von einem Review fuer Anwendungscode, weil dieselbe Anweisung je nach Datenmenge, Indexlage und gleichzeitigem Zugriff vollkommen unterschiedliches Verhalten zeigen kann. Eine Anwendungsfunktion, die in einem Unit-Test korrekt durchlaeuft, verhaelt sich in Produktion identisch. Eine SQL-Abfrage, die gegen eine leere Testdatenbank in Millisekunden laeuft, kann gegen eine Produktionstabelle mit zehn Millionen Zeilen minutenlang blockieren, ohne dass der Code selbst syntaktisch falsch waere.
Diese Eigenheit macht eine dedizierte SQL-Code-Review-Checkliste notwendig, die ueber die ueblichen Kriterien fuer Anwendungscode hinausgeht: Lesbarkeit ist notwendig, aber nicht ausreichend. Ein SQL-Review muss zusaetzlich NULL-Semantik, Indexnutzung, Transaktionsgrenzen und Injection-Risiken systematisch abdecken, sonst bleiben genau die Fehler unentdeckt, die im Anwendungscode-Review niemand sucht, weil sie dort schlicht nicht vorkommen. Die folgenden Abschnitte bauen diese Checkliste Schritt fuer Schritt auf.
2. Lesbarkeit und Formatierung als Basis jeder Review
Bevor ein Reviewer inhaltlich in eine SQL-Aenderung einsteigt, lohnt sich ein kurzer Blick auf Formatierung und Namensgebung, weil schlecht lesbarer SQL-Code inhaltliche Fehler zusaetzlich verschleiert. Konsistente Gross- und Kleinschreibung von Schluesselwoertern, sprechende Alias-Namen statt a, b, c, und eine klare Einrueckung bei mehrzeiligen JOIN-Ketten machen eine Abfrage ueberhaupt erst pruefbar. Ein Reviewer, der zehn Minuten braucht, um die Struktur einer Abfrage zu entziffern, hat weniger Kapazitaet, um die eigentliche Logik zu hinterfragen.
Ein zweiter Aspekt der Lesbarkeit betrifft explizite Spaltenlisten statt SELECT *. Eine Abfrage mit SELECT * ist nicht nur schwerer nachzuvollziehen, weil unklar bleibt, welche Spalten die Anwendung tatsaechlich braucht, sondern auch fragiler: Wird spaeter eine Spalte zur Tabelle hinzugefuegt, aendert sich das Ergebnis der Abfrage stillschweigend, ohne dass der Code selbst angefasst wurde. Ein Reviewer sollte SELECT * in produktivem Code grundsaetzlich hinterfragen, ausser in expliziten Ausnahmefaellen wie Existenzpruefungen mit EXISTS.
-- REVIEW FLAG: unreadable, unmaintainable
SELECT a.*, b.*
FROM orders a, customers b
WHERE a.customer_id = b.id AND a.status = 'paid';
-- BETTER: explicit columns, explicit JOIN, readable aliases
SELECT
orders.id AS order_id,
orders.total_amount,
customers.email AS customer_email
FROM orders
INNER JOIN customers ON customers.id = orders.customer_id
WHERE orders.status = 'paid';
3. Korrektheit: NULL-Handling, Joins und Aggregation pruefen
NULL-Werte sind die haeufigste Quelle stiller Logikfehler in SQL, weil sie sich nicht wie ein normaler Wert verhalten. Ein Vergleich WHERE spalte = NULL liefert niemals wahr, selbst wenn die Spalte tatsaechlich NULL ist, und ein Reviewer, der diesen Fehler nicht kennt, laesst ihn durchgehen. Auch NOT IN mit einer Unterabfrage, die auch nur eine einzige NULL-Zeile liefert, gibt ueberraschenderweise gar keine Zeile zurueck, ein Klassiker unter den SQL-Fallen, den jede Checkliste explizit abdecken sollte.
Bei Joins ist zu pruefen, ob der gewaehlte Join-Typ tatsaechlich der gewuenschten Semantik entspricht. Ein INNER JOIN, wo eigentlich ein LEFT JOIN gemeint war, filtert stillschweigend Zeilen heraus, deren zugehoerige Kind-Zeilen fehlen, etwa Kunden ohne Bestellungen in einem Bericht, der eigentlich alle Kunden zeigen soll. Bei Aggregationen mit GROUP BY sollte der Reviewer pruefen, ob alle nicht aggregierten Spalten in der GROUP BY-Klausel stehen, weil manche Datenbanken das nicht erzwingen und dadurch unvorhersehbare Ergebnisse aus mehrdeutigen Gruppierungen entstehen koennen.
-- REVIEW FLAG: NOT IN with a subquery that can return NULL
-- If ANY row in the subquery has a NULL customer_id, this returns ZERO rows
SELECT * FROM customers
WHERE id NOT IN (SELECT customer_id FROM orders);
-- BETTER: NOT EXISTS handles NULL correctly
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.id
);
-- REVIEW FLAG: comparing to NULL never matches
SELECT * FROM customers WHERE deleted_at = NULL; -- always empty result
-- CORRECT: use IS NULL
SELECT * FROM customers WHERE deleted_at IS NULL;
4. Performance-Red-Flags im Review erkennen
Ein Reviewer muss nicht bei jeder Abfrage einen vollstaendigen EXPLAIN-Plan analysieren, sollte aber bestimmte Muster als sofortige Red Flags erkennen. Eine Funktion auf der linken Seite einer WHERE-Bedingung, etwa WHERE YEAR(created_at) = 2026, verhindert in den meisten Datenbanksystemen die Nutzung eines Index auf created_at, weil der Index auf den Rohwert, nicht auf das Funktionsergebnis, aufgebaut ist. Ein LIKE '%suchbegriff%' mit fuehrendem Wildcard verhindert ebenfalls Index-Nutzung und sollte bei groesseren Tabellen hinterfragt werden.
Ein zweites haeufiges Muster ist das N+1-Problem: eine Schleife im Anwendungscode, die pro Element eine eigene Abfrage gegen die Datenbank absetzt, statt alle benoetigten Daten in einer einzigen Abfrage mit JOIN oder WHERE id IN (...) zu laden. Dieses Muster ist im SQL-Code selbst oft nicht sichtbar, sondern nur im umgebenden Anwendungscode, weshalb ein SQL-Review idealerweise den Aufrufkontext mit einbezieht, nicht nur die isolierte Abfrage.
-- REVIEW FLAG: function on indexed column prevents index usage
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- BETTER: range condition keeps the index usable
SELECT * FROM orders
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
-- REVIEW FLAG: leading wildcard prevents index usage
SELECT * FROM products WHERE name LIKE '%widget%';
-- BETTER: full-text index or trailing wildcard where semantics allow it
SELECT * FROM products WHERE name LIKE 'widget%';
5. Sicherheit: Injection und Rechtevergabe im Review pruefen
SQL-Injection bleibt eine der haeufigsten kritischen Schwachstellen, obwohl die Loesung seit Jahrzehnten bekannt ist: Parameterisierte Abfragen statt String-Konkatenation von Nutzereingaben. Ein Reviewer muss jede Stelle im Diff identifizieren, an der eine Variable direkt in einen SQL-String eingefuegt wird, statt als gebundener Parameter uebergeben zu werden, unabhaengig davon, wie unwahrscheinlich ein Angriff an dieser Stelle erscheint. Auch dynamisch zusammengesetzte Tabellen- oder Spaltennamen, die nicht parametrisierbar sind, muessen gegen eine feste Allowlist geprueft werden, statt Nutzereingaben ungeprueft zu uebernehmen.
Neben der Injection-Pruefung gehoert die Rechtevergabe in jede SQL-Code-Review-Checkliste. Ein neuer Datenbank-Nutzer oder ein neues Skript, das mit weitreichenden Rechten wie GRANT ALL arbeitet, obwohl nur lesender Zugriff auf zwei Tabellen benoetigt wird, verletzt das Prinzip der geringsten Rechte und vergroessert die Angriffsflaeche im Fall eines kompromittierten Zugangs unnoetig. Ein Reviewer sollte jede GRANT-Anweisung explizit gegen den tatsaechlichen Bedarf pruefen.
-- REVIEW FLAG: overly broad privilege grant for a reporting service account
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO reporting_service;
-- BETTER: least privilege — only what the service actually reads
GRANT SELECT ON orders, order_items, customers TO reporting_service;
REVOKE ALL ON audit_log FROM reporting_service;
-- REVIEW FLAG: string concatenation, classic injection vector
-- query = "SELECT * FROM customers WHERE email = '" + userInput + "'"
-- BETTER: parameterized query, user input never touches the SQL string
-- query = "SELECT * FROM customers WHERE email = $1"; params = [userInput]
6. Transaktionsgrenzen und Nebenlaeufigkeit bewerten
Transaktionsgrenzen sind im Code-Review leicht zu uebersehen, weil sie oft implizit durch das Framework gesetzt werden, aber massive Auswirkungen auf Nebenlaeufigkeit und Datenintegritaet haben. Eine Transaktion, die zu weit gefasst ist und dabei externe, langsame Operationen wie einen HTTP-Aufruf einschliesst, haelt Datenbank-Locks unnoetig lange und blockiert andere Transaktionen. Eine Transaktion, die zu eng gefasst ist und mehrere zusammenhaengende Schreiboperationen auf getrennte Transaktionen verteilt, riskiert Inkonsistenzen, wenn die zweite Operation fehlschlaegt, nachdem die erste bereits committet wurde.
Bei nebenlaeufigen Schreiboperationen sollte ein Reviewer pruefen, ob Race Conditions moeglich sind, etwa ein SELECT gefolgt von einem separaten UPDATE, zwischen denen ein anderer Prozess denselben Datensatz aendern kann. In solchen Faellen ist entweder ein atomarer UPDATE ... WHERE mit Bedingung auf den erwarteten Ausgangswert, oder eine explizite Sperre mit SELECT ... FOR UPDATE notwendig, je nach Isolation Level der Datenbank.
-- REVIEW FLAG: read-then-write race condition
SELECT stock_quantity FROM products WHERE id = 42;
-- ... application checks stock_quantity > 0, then in a SEPARATE statement:
UPDATE products SET stock_quantity = stock_quantity - 1 WHERE id = 42;
-- Between the two statements, another transaction can also decrement stock
-- BETTER: atomic conditional update, no race condition possible
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE id = 42 AND stock_quantity > 0;
-- Check affected row count in the application: 0 rows means out of stock
7. Migrations- und Schema-Aenderungen im Review absichern
Schema-Migrationen brauchen im Review eine eigene, strengere Pruefung als reine Datenabfragen, weil ein Fehler hier nicht nur ein falsches Ergebnis, sondern einen Ausfall oder Datenverlust bedeuten kann. Ein Reviewer sollte pruefen, ob die Migration einen Rollback-Pfad hat, ob destructive Operationen wie DROP COLUMN in einer separaten, spaeteren Migration nach einer Uebergangsphase erfolgen, und ob neue NOT NULL-Constraints einen sinnvollen Standardwert fuer bestehende Zeilen mitbringen.
Zusaetzlich gehoert eine Einschaetzung des Lock-Verhaltens in jede Migrations-Review: Bei grossen Tabellen sollte der Reviewer explizit hinterfragen, ob die Migration einen kurzen Metadaten-Change oder einen langen Table-Rewrite ausloest, und ob dieser Umstand mit dem Betriebsteam abgestimmt wurde, bevor die Migration in einem Wartungsfenster oder waehrend Nebenlastzeiten eingeplant wird.
8. Die Checkliste als wiederverwendbares Werkzeug etablieren
Eine Checkliste, die nur im Kopf des erfahrensten Entwicklers existiert, skaliert nicht auf ein wachsendes Team. Der naechste Schritt ist, die genannten Kriterien in eine dokumentierte, versionierte Pull-Request-Vorlage zu ueberfuehren, die bei jeder SQL-Aenderung automatisch als Checkbox-Liste erscheint. Das reduziert die Abhaengigkeit von einzelnen Reviewern und macht den Reviewprozess fuer neue Teammitglieder nachvollziehbar, ohne dass Wissen muendlich weitergegeben werden muss.
Wichtig ist, die Checkliste lebendig zu halten: Jeder produktionsrelevante Vorfall, der auf einen uebersehenen SQL-Fehler zurueckgeht, sollte als neuer Punkt in die Checkliste einfliessen, damit derselbe Fehlertyp beim naechsten Review garantiert auffaellt. Diese kontinuierliche Erweiterung macht aus einer statischen Liste ein lernendes System, das mit den tatsaechlichen Fehlermustern des Teams mitwaechst.
9. Automatisierte Linter versus manuelle Review im Vergleich
Nicht jeder Punkt der Checkliste muss manuell geprueft werden. Statische SQL-Linter wie sqlfluff oder datenbankspezifische Analyzer decken einen Teil der Kriterien automatisiert ab und entlasten den menschlichen Reviewer fuer die Punkte, die echtes Kontextwissen erfordern.
| Kriterium | Automatisiert pruefbar | Braucht manuelle Review |
|---|---|---|
| Formatierung, Namenskonvention | Ja, per Linter | Nein |
| SELECT * Nutzung | Ja, per Linter-Regel | Ausnahmefaelle bewerten |
| NULL-Handling-Logikfehler | Teilweise | Ja, immer |
| Index-Nutzung, EXPLAIN-Analyse | Nein, ohne Live-Daten | Ja, immer |
| SQL-Injection-Risiko | Teilweise, statische Analyse | Ja, immer |
| Transaktionsgrenzen, Race Conditions | Nein | Ja, immer |
Der pragmatische Ansatz kombiniert beide Ebenen: Linter fangen die mechanischen, eindeutig regelbasierten Verstoesse automatisch ab, bevor ein menschlicher Reviewer ueberhaupt einen Blick auf den Pull Request wirft. Das entlastet den Reviewer, sich auf die Punkte zu konzentrieren, die echtes Verstaendnis von Datenverteilung, Nebenlaeufigkeit und Systemarchitektur erfordern, statt Zeit mit Formatierungsdiskussionen zu verbringen.
Mironsoft
SQL-Qualitaetssicherung, Code-Reviews und Datenbankarchitektur fuer Magento und darueber hinaus
SQL-Reviews, die kritische Fehler nicht durchwinken?
Wir etablieren dokumentierte SQL-Review-Checklisten, kombinieren automatisierte Linter mit gezielter manueller Pruefung und schulen Teams auf die haeufigsten SQL-Fallen.
Checklisten-Design
Individuelle SQL-Review-Checklisten fuer Pull-Request-Vorlagen entwickeln
Linter-Integration
sqlfluff und Static-Analysis-Tools in bestehende CI-Pipelines einbinden
Team-Schulung
Workshops zu NULL-Handling, Injection-Risiken und Performance-Red-Flags
10. Zusammenfassung
Eine SQL-Code-Review-Checkliste macht aus einem subjektiven Bauchgefuehl einen reproduzierbaren, teamweiten Standard. Lesbarkeit und Formatierung sind der Einstieg, aber die eigentliche Wirkung entsteht durch systematische Pruefung von NULL-Handling, Join-Semantik, Performance-Red-Flags wie Funktionen auf indexierten Spalten, Injection-Risiken und Transaktionsgrenzen.
Migrations- und Schema-Aenderungen verdienen eine eigene, strengere Pruefstufe, weil Fehler hier Datenverlust statt nur falsche Ergebnisse bedeuten koennen. Automatisierte Linter uebernehmen die mechanischen, regelbasierten Kriterien und entlasten den menschlichen Reviewer fuer die Punkte, die echtes Kontextwissen erfordern. Wer die Checkliste dokumentiert, versioniert und nach jedem produktionsrelevanten Vorfall erweitert, baut ein lernendes System auf, das mit dem Team mitwaechst.
SQL Code-Review-Checkliste — Das Wichtigste auf einen Blick
NULL-Handling
NOT IN mit Subqueries, die NULL enthalten koennen, und Vergleiche mit NULL sind Klassiker unter den SQL-Fallen.
Performance-Red-Flags
Funktionen auf indexierten Spalten und fuehrende Wildcards in LIKE verhindern Index-Nutzung.
Sicherheit
Parameterisierte Abfragen statt String-Konkatenation, Rechtevergabe nach Prinzip der geringsten Rechte.
Automatisierung
Linter fuer mechanische Kriterien, manuelle Review fuer Kontextwissen wie Nebenlaeufigkeit.