9 points par GN⁺ 2025-04-21 | 1 commentaires | Partager sur WhatsApp
  • Les tables virtuelles SQLite peuvent aussi prendre en charge l’écriture et les transactions, en implémentant des hooks comme xUpdate, xSync, xCommit, xRollback, etc.
  • SQLite garantit l’atomicité via le mécanisme de journal de rollback par défaut, et lorsqu’il manipule plusieurs fichiers de base de données, il coordonne le commit global avec un super-journal.
  • Les tables virtuelles sont elles aussi intégrées au protocole de transaction de SQLite : si xSync échoue, toute la transaction est annulée.
  • Le commit se décompose en deux étapes : xSync pour les opérations susceptibles d’échouer, puis xCommit qui ne doit faire qu’un simple travail de nettoyage.
  • xCommit et xRollback pouvant toujours être appelés, ils doivent être écrits comme des fonctions de nettoyage capables de s’exécuter sans échec.

Tables virtuelles et traitement des transactions dans SQLite

Dans un article précédent, les bases de l’enregistrement et de l’interrogation de tables virtuelles SQLite en Go étaient présentées. Cet article traite cette fois de la manière d’implémenter des tables virtuelles inscriptibles avec prise en charge des transactions.

Prise en charge de l’écriture et des transactions dans les tables virtuelles

  • L’interface des tables virtuelles de SQLite n’est pas en lecture seule

  • En implémentant le hook xUpdate, il devient possible d’écrire aussi vers une source de données externe

  • Pour obtenir une véritable cohérence transactionnelle, les hooks suivants sont nécessaires :

    • xBegin : notification de début de transaction
    • xSync : préparation d’un commit sûr sur disque (si cela échoue ici, tout est annulé)
    • xCommit : commit final et nettoyage
    • xRollback : exécution du rollback si la transaction est interrompue
  • Même lorsque des modifications sont effectuées avec des tables ordinaires ou d’autres tables virtuelles, SQLite coordonne tous les hooks pour garantir l’atomicité

Fonctionnement interne des transactions SQLite

Journal de rollback (Rollback Journals)

  • SQLite enregistre d’abord les pages dans un fichier de sauvegarde (journal) avant de les écraser
  • En cas de problème, il restaure depuis le journal afin de garantir l’atomicité

> Remarque : SQLite prend aussi en charge le mode WAL, mais il n’entre pas dans le cadre de cet article

Super-journal (Super-Journals)

  • Lorsque plusieurs bases de données sont attachées, il est difficile de les synchroniser uniquement avec des journaux individuels pour chaque base

  • SQLite coordonne le commit entre plusieurs fichiers via un fichier de niveau supérieur appelé super-journal

  • Lorsqu’on ne manipule que plusieurs tables virtuelles dans un même fichier de base de données, la synchronisation reste possible sans super-journal

  • Dans tous les cas, SQLite appelle automatiquement les hooks xSync, xCommit et xRollback dans le flux transactionnel

Commit en deux phases avec les tables virtuelles

Le processus de commit de SQLite se déroule en deux phases :

Phase 1 : xSync (garantie de durabilité)

  • Toutes les pages ou tous les journaux des B-Tree et des fichiers de base de données sont synchronisés de manière sûre sur disque
  • Chaque table virtuelle voit également son hook xSync appelé
  • Si un seul xSync échoue, toute la transaction est annulée → l’atomicité est préservée

Phase 2 : nettoyage (xCommit)

  • Une fois l’écriture sur disque terminée, le fichier journal est supprimé et le nettoyage des tables virtuelles est effectué

  • Voici un extrait du code de vdbeaux.c

    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);  
    
  • Dans sqlite3VtabCommit(), tous les appels à xCommit sont en pratique ignorés même s’ils échouent : il s’agit d’une phase de nettoyage pur

    int sqlite3VtabCommit(sqlite3 *db){  
      callFinaliser(db, offsetof(sqlite3_module,xCommit));  
      return SQLITE_OK;  
    }  
    
  • Comme la durabilité a déjà été assurée par xSync, les échecs de xCommit et xRollback sont ignorés

Points d’attention pour les auteurs de tables virtuelles

  • Les opérations persistantes doivent impérativement être placées dans xSync
    • Les opérations susceptibles d’échouer, comme les E/S réseau ou l’écriture de fichiers, doivent être traitées ici afin que la transaction puisse être interrompue en toute sécurité
  • xRollback peut encore être appelé après xSync
    • Si le xSync d’une autre table échoue, toute la transaction est annulée
  • xCommit et xRollback doivent être écrits comme des fonctions de nettoyage qui n’échouent pas
    • Ils doivent être idempotents, c’est-à-dire ne pas modifier l’état même s’ils sont appelés plusieurs fois

Conclusion

  • Le mécanisme de journalisation de SQLite garantit un commit atomique pour tous les éléments, y compris les tables ordinaires et les tables virtuelles
  • Les hooks transactionnels des tables virtuelles sont naturellement intégrés au flux transactionnel de SQLite
  • Les développeurs qui implémentent des tables virtuelles doivent se concentrer sur xSync pour garantir l’intégrité des données, puis répartir le travail de nettoyage entre xCommit et xRollback

1 commentaires

 
GN⁺ 2025-04-21
Commentaire Hacker News
  • Ravi de voir un article sur les vtabs. J’ai implémenté le support des vtab en réécrivant SQLite en Rust. J’ai donc beaucoup appris à leur sujet récemment. Les vtab sont très puissantes et probablement sous-exploitées
  • Intéressant. Mais cela utilise le package go-sqlite3 de mattn. C’est du CGO
    • Je me demande si, dans le Go moderne, c’est une exigence courante ou attendue