9 Punkte von GN⁺ 2025-04-21 | 1 Kommentare | Auf WhatsApp teilen
  • Auch virtuelle Tabellen in SQLite unterstützen Schreibzugriffe und Transaktionen; dazu werden Hooks wie xUpdate, xSync, xCommit und xRollback implementiert und verwendet
  • SQLite gewährleistet standardmäßig Atomarität über das Rollback-Journal-Verfahren; bei mehreren DB-Dateien koordiniert ein Super-Journal den gesamten Commit
  • Auch virtuelle Tabellen sind in das Transaktionsprotokoll von SQLite eingebunden; schlägt xSync fehl, wird die gesamte Transaktion zurückgerollt
  • Der Commit ist in zwei Phasen aufgeteilt; xSync ist für potenziell fehleranfällige Arbeiten zuständig, während xCommit nur einfache Aufräumarbeiten ausführen sollte
  • xCommit und xRollback können immer aufgerufen werden und sollten daher als fehlerfreie Aufräumfunktionen implementiert werden

Virtuelle Tabellen und Transaktionsverarbeitung in SQLite

Im vorherigen Artikel wurden die grundlegenden Methoden vorgestellt, um virtuelle Tabellen in SQLite mit Go zu registrieren und abzufragen. Dieser Artikel behandelt nun, wie man beschreibbare virtuelle Tabellen mit Transaktionsunterstützung implementiert.

Schreib- und Transaktionsunterstützung für virtuelle Tabellen

  • Die Schnittstelle für virtuelle Tabellen in SQLite ist nicht schreibgeschützt

  • Wenn der Hook xUpdate implementiert wird, sind Schreibzugriffe auch auf externe Datenquellen möglich

  • Für echte transaktionale Konsistenz werden die folgenden Transaktions-Hooks benötigt:

    • xBegin: Benachrichtigung über den Beginn einer Transaktion
    • xSync: Vorbereitung für einen sicheren Commit auf die Festplatte (schlägt dies fehl, erfolgt ein vollständiges Rollback)
    • xCommit: Finaler Commit und Aufräumen
    • xRollback: Führt ein Rollback aus, wenn die Transaktion abgebrochen wurde
  • Selbst wenn zusammen mit normalen Tabellen oder anderen virtuellen Tabellen Änderungen vorgenommen werden, verknüpft SQLite alle Hooks, um Atomarität sicherzustellen

Interne Funktionsweise von SQLite-Transaktionen

Rollback-Journale

  • SQLite speichert standardmäßig Seiten vor dem Überschreiben in einer Sicherungsdatei (Journal)
  • Tritt ein Problem auf, wird aus dem Journal wiederhergestellt, wodurch Atomarität gewährleistet wird

Hinweis: SQLite unterstützt auch den WAL-Modus, dieser liegt jedoch außerhalb des Umfangs dieses Artikels

Super-Journale

  • Wenn mehrere Datenbanken eingebunden sind, ist eine Synchronisierung allein mit einzelnen Journalen pro DB schwierig

  • Eine übergeordnete Datei namens Super-Journal koordiniert den Commit über mehrere Dateien hinweg

  • Wenn nur mehrere virtuelle Tabellen innerhalb einer einzelnen DB-Datei behandelt werden, ist auch ohne Super-Journal eine Synchronisierung möglich

  • In jedem Fall ruft SQLite die Hooks xSync, xCommit und xRollback innerhalb des Transaktionsablaufs automatisch auf

Two-Phase-Commit mit virtuellen Tabellen

Der Commit-Prozess in SQLite besteht aus zwei Phasen:

Phase 1: xSync (Gewährleistung von Durability)

  • Alle Seiten oder Journale aller B-Bäume und DB-Dateien werden sicher auf die Festplatte synchronisiert
  • Auch für virtuelle Tabellen wird jeweils der Hook xSync aufgerufen
  • Wenn auch nur ein xSync fehlschlägt, wird die gesamte Transaktion zurückgerollt → Atomarität bleibt erhalten

Phase 2: Aufräumen (xCommit)

  • Sobald das Schreiben auf die Festplatte abgeschlossen ist, werden Journal-Dateien gelöscht und Aufräumarbeiten für virtuelle Tabellen ausgeführt

  • Unten ist ein Teil des Codes aus vdbeaux.c zu sehen

    disable_simulated_io_errors();  
    sqlite3BeginBenignMalloc();  
    for(i=0; i<db->nDb; i++){  
      Btree *pBt = db->aDb[i].pBt;  
      if( pBt ){  
        sqlite3BtreeCommitPhaseTwo(pBt, 1);  
      }  
    }  
    sqlite3EndBenignMalloc();  
    enable_simulated_io_errors();  
    sqlite3VtabCommit(db);  
    
  • Innerhalb von sqlite3VtabCommit() werden tatsächlich alle xCommit-Aufrufe ignoriert, selbst wenn sie fehlschlagen → eine reine Aufräumphase

    int sqlite3VtabCommit(sqlite3 *db){  
      callFinaliser(db, offsetof(sqlite3_module,xCommit));  
      return SQLITE_OK;  
    }  
    
  • Da die Dauerhaftigkeit bereits durch xSync sichergestellt wurde, werden Fehler in xCommit und xRollback ignoriert

Hinweise für Entwickler virtueller Tabellen

  • Dauerhafte Arbeiten müssen unbedingt in xSync erfolgen
    • Fehleranfällige Operationen wie Netzwerk-I/O oder Dateischreibvorgänge sollten hier stattfinden, damit die Transaktion sicher abgebrochen werden kann
  • Auch nach xSync kann noch xRollback aufgerufen werden
    • Wenn xSync einer anderen Tabelle fehlschlägt, wird die gesamte Transaktion zurückgerollt
  • xCommit und xRollback sollten als fehlerfreie Aufräumfunktionen implementiert werden
    • Sie sollten idempotent sein, also auch bei mehrfachen Aufrufen keinen Zustandswechsel verursachen

Fazit

  • Der Journaling-Mechanismus von SQLite gewährleistet atomare Commits für alle Elemente, einschließlich normaler und virtueller Tabellen
  • Die Transaktions-Hooks virtueller Tabellen sind nahtlos in den Transaktionsablauf von SQLite integriert
  • Entwickler virtueller Tabellen sollten sich auf xSync konzentrieren, um Datenintegrität sicherzustellen, und Aufräumarbeiten auf xCommit und xRollback aufteilen

1 Kommentare

 
GN⁺ 2025-04-21
Hacker-News-Kommentar
  • Guter Artikel über vtabs. Ich habe SQLite in Rust neu implementiert und dabei die Unterstützung für vtabs umgesetzt. Deshalb habe ich in letzter Zeit viel über vtabs gelernt. Sie sind sehr leistungsfähig und werden vermutlich nicht ausreichend genutzt.
  • Interessant. Allerdings verwendet das hier mattns Paket go-sqlite3. Das ist CGO.
    • Ich frage mich, ob das im modernen Go eine übliche oder erwartete Anforderung ist.