9 puntos por GN⁺ 2025-04-21 | 1 comentarios | Compartir por WhatsApp
  • Las tablas virtuales de SQLite también pueden admitir escritura y transacciones, usando hooks como xUpdate, xSync, xCommit y xRollback
  • SQLite garantiza la atomicidad con el modo de diario de rollback de forma predeterminada, y al manejar varios archivos de BD usa un super-journal para coordinar el commit completo
  • Las tablas virtuales también forman parte del protocolo de transacciones de SQLite, y si xSync falla se hace rollback de toda la transacción
  • El commit se divide en 2 fases: xSync para trabajo que puede fallar, y xCommit solo debe encargarse de tareas simples de limpieza
  • xCommit y xRollback siempre pueden ser llamados, por lo que deben implementarse como funciones de limpieza que puedan ejecutarse sin fallar

Tablas virtuales y manejo de transacciones en SQLite

En el artículo anterior se presentó la forma básica de registrar y consultar tablas virtuales de SQLite usando Go. En este artículo se explica cómo implementar tablas virtuales con soporte de escritura y transacciones.

Soporte de escritura y transacciones en tablas virtuales

  • La interfaz de tablas virtuales de SQLite no es solo de lectura

  • Si se implementa el hook xUpdate, también se puede escribir en una fuente de datos externa

  • Para una consistencia transaccional real, se necesitan los siguientes hooks de transacción:

    • xBegin: aviso de inicio de transacción
    • xSync: preparación para hacer commit seguro en disco (si falla aquí, se revierte toda la transacción)
    • xCommit: commit final y limpieza
    • xRollback: ejecuta el rollback si la transacción fue abortada
  • Incluso cuando se modifican junto con tablas normales u otras tablas virtuales, SQLite coordina todos los hooks para garantizar la atomicidad

Cómo funcionan internamente las transacciones en SQLite

Diarios de rollback (Rollback Journals)

  • SQLite, de forma predeterminada, guarda las páginas en un archivo de respaldo (journal) antes de sobrescribirlas
  • Si ocurre un problema, se recupera desde el journal para garantizar la atomicidad

Nota: SQLite también soporta el modo WAL, pero queda fuera del alcance de este artículo

Super-journals

  • Cuando hay varias bases de datos conectadas, es difícil sincronizarlas usando solo journals individuales por cada BD

  • Se coordina el commit entre varios archivos mediante un archivo de nivel superior llamado super-journal

  • Si solo se manejan varias tablas virtuales dentro de un mismo archivo de BD, es posible sincronizarlas sin super-journal

  • En cualquier caso, SQLite llama automáticamente a los hooks xSync, xCommit y xRollback dentro del flujo de la transacción

Commit en 2 fases con tablas virtuales

El proceso de commit en SQLite se realiza en dos fases:

Fase 1: xSync (garantiza la durabilidad)

  • Sincroniza de forma segura en disco las páginas o journals de todos los B-Tree y archivos de BD
  • También se llama al hook xSync de cada tabla virtual
  • Si falla cualquier xSync, se hace rollback de toda la transacción → se conserva la atomicidad

Fase 2: limpieza (xCommit)

  • Una vez que el guardado en disco se completa, se eliminan los archivos de journal y se ejecuta la limpieza de las tablas virtuales

  • A continuación se muestra una parte del código 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);  
    
  • Dentro de sqlite3VtabCommit(), en la práctica se ignora incluso si fallan todas las llamadas a xCommit → es una fase puramente de limpieza

    int sqlite3VtabCommit(sqlite3 *db){  
      callFinaliser(db, offsetof(sqlite3_module,xCommit));  
      return SQLITE_OK;  
    }  
    
  • Como la durabilidad ya quedó asegurada con xSync, si xCommit o xRollback fallan, se ignoran

Puntos a tener en cuenta para autores de tablas virtuales

  • Toda operación persistente debe ir obligatoriamente en xSync
    • Trabajo que puede fallar, como I/O de red o escritura de archivos, debe hacerse aquí para que la transacción pueda abortarse de forma segura
  • Incluso después de xSync, puede llamarse a xRollback
    • Si el xSync de otra tabla falla, se revierte toda la transacción
  • xCommit y xRollback deben implementarse como funciones de limpieza que no fallen
    • Deben ser idempotentes, es decir, no cambiar el estado aunque se llamen varias veces

Conclusión

  • El mecanismo de journaling de SQLite garantiza commits atómicos para todos los elementos, incluidas las tablas normales y las virtuales
  • Los hooks de transacción de las tablas virtuales se integran de forma natural en el flujo transaccional de SQLite
  • Quien implemente tablas virtuales debe concentrarse en xSync para asegurar la integridad de los datos y separar las tareas de limpieza en xCommit y xRollback

1 comentarios

 
GN⁺ 2025-04-21
Comentarios en Hacker News
  • Está bueno ver una publicación sobre vtabs. Implementé soporte para vtab al reimplementar SQLite en Rust, así que últimamente he aprendido mucho sobre vtab. Son muy potentes y probablemente no se aprovechan lo suficiente
  • Interesante. Pero esto usa el paquete go-sqlite3 de mattn. Eso es CGO
    • Me pregunto si en el GO moderno esto es un requisito común o esperable