9 pontos por GN⁺ 2025-04-21 | 1 comentários | Compartilhar no WhatsApp
  • As tabelas virtuais do SQLite também podem oferecer suporte a escrita e transações, implementando hooks como xUpdate, xSync, xCommit e xRollback
  • O SQLite garante a atomicidade por padrão com o mecanismo de journal de rollback e, ao lidar com vários arquivos de banco de dados, coordena o commit geral com um super-journal
  • As tabelas virtuais também fazem parte do protocolo de transação do SQLite, de modo que, se xSync falhar, toda a transação é revertida
  • O commit é dividido em duas etapas: xSync para operações que podem falhar e xCommit apenas para limpeza simples
  • xCommit e xRollback sempre podem ser chamados, então devem ser escritos como funções de limpeza que executam sem falha

Tabelas virtuais e processamento de transações no SQLite

No artigo anterior, foi apresentada a forma básica de registrar e consultar tabelas virtuais no SQLite usando a linguagem Go. Neste texto, o foco é como implementar tabelas virtuais com suporte a escrita e transações.

Suporte a escrita e transações em tabelas virtuais

  • A interface de tabelas virtuais do SQLite não é somente leitura

  • Ao implementar o hook xUpdate, também é possível gravar em fontes de dados externas

  • Para uma consistência transacional real, são necessários os seguintes hooks de transação:

    • xBegin: notifica o início da transação
    • xSync: prepara um commit seguro em disco (se falhar aqui, toda a transação é revertida)
    • xCommit: commit final e limpeza
    • xRollback: executa o rollback caso a transação tenha sido interrompida
  • Mesmo quando há modificações junto com tabelas comuns ou outras tabelas virtuais, o SQLite coordena todos os hooks para garantir atomicidade

Como as transações do SQLite funcionam internamente

Journals de rollback (Rollback Journals)

  • Por padrão, o SQLite salva as páginas em um arquivo de backup (journal) antes de sobrescrevê-las
  • Se algo der errado, ele se recupera a partir do journal para garantir atomicidade

> Observação: o SQLite também oferece suporte ao modo WAL, mas isso fica fora do escopo deste texto

Super-journals

  • Quando vários bancos de dados estão anexados, é difícil sincronizar tudo usando apenas journals individuais de cada banco

  • Um arquivo de nível superior chamado super-journal coordena o commit entre vários arquivos

  • Se o caso envolver apenas várias tabelas virtuais dentro de um único arquivo de banco, a sincronização pode ocorrer sem super-journal

  • Em qualquer cenário, o SQLite chama automaticamente os hooks xSync, xCommit e xRollback dentro do fluxo da transação

Commit em duas fases com tabelas virtuais

O processo de commit no SQLite ocorre em duas etapas:

1ª etapa: xSync (garantia de durabilidade)

  • Todas as páginas ou journals de todos os B-Trees e arquivos de banco de dados são sincronizados com segurança no disco
  • O hook xSync também é chamado para cada tabela virtual
  • Se qualquer xSync falhar, toda a transação é revertida → a atomicidade é preservada

2ª etapa: limpeza (xCommit)

  • Quando a gravação em disco termina, os arquivos de journal são removidos e a limpeza das tabelas virtuais é executada

  • Abaixo está um trecho 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(), na prática, mesmo que todas as chamadas de xCommit falhem, isso é ignorado → é uma etapa puramente de limpeza

    int sqlite3VtabCommit(sqlite3 *db){  
      callFinaliser(db, offsetof(sqlite3_module,xCommit));  
      return SQLITE_OK;  
    }  
    
  • Como a durabilidade já foi garantida por xSync, falhas em xCommit e xRollback são ignoradas

Pontos de atenção para quem implementa tabelas virtuais

  • Operações persistentes devem obrigatoriamente ficar em xSync
    • I/O de rede, escrita em arquivo e outras operações que podem falhar devem ser tratadas aqui para que a transação possa ser interrompida com segurança
  • Mesmo depois de xSync, xRollback ainda pode ser chamado
    • Se o xSync de outra tabela falhar, toda a transação será revertida
  • xCommit e xRollback devem ser escritos como funções de limpeza que não falham
    • Devem ser idempotentes, ou seja, chamadas múltiplas não devem alterar o estado

Conclusão

  • O mecanismo de journaling do SQLite garante commits atômicos para todos os elementos, incluindo tabelas comuns e tabelas virtuais
  • Os hooks de transação das tabelas virtuais são integrados de forma natural ao fluxo de transações do SQLite
  • Quem implementa tabelas virtuais deve concentrar as operações persistentes em xSync para garantir a integridade dos dados, separando a limpeza entre xCommit e xRollback

1 comentários

 
GN⁺ 2025-04-21
Comentários no Hacker News
  • Gostei de ver um post sobre vtabs. Reimplementei o SQLite em Rust e implementei suporte a vtab, então aprendi bastante sobre vtab recentemente. vtab é muito poderoso e provavelmente não é usado o suficiente
  • Interessante. Mas isto usa o pacote mattn go-sqlite3. Isso é CGO
    • Fico curioso se isso é um requisito comum ou esperado no Go moderno