1. Panoramica2. Come funziona il WAL2.1. Checkpointing2.2. Concorrenza2.3. Considerazioni sulle prestazioni3. Attivazione e configurazione della modalità WAL3.1. Checkpoint automatico3.2. Checkpoint avviati dall'applicazione3.3. Persistenza della modalità WAL4. Il file WAL5. Database di sola lettura6. Evitare file WAL troppo grandi7. Implementazione della memoria condivisa per il WAL-Index8. Uso del WAL senza memoria condivisa9. A volte le query restituiscono SQLITE_BUSY in modalità WAL10. Compatibilità con le versioni precedenti

Il metodo predefinito con cui SQLite implementa il commit e il rollback atomico è un journal di rollback. A partire da versione 3.7.0 (2010-07-21), è disponibile una nuova opzione "Write-Ahead Log" (di seguito denominata "WAL").

L'uso di WAL al posto di un giornale di rollback presenta vantaggi e svantaggi. I vantaggi includono:

  1. WAL è significativamente più veloce nella maggior parte degli scenari.
  2. Il WAL offre una maggiore concorrenza, poiché i lettori non bloccano gli scrittori e gli scrittori non bloccano i lettori. La lettura e la scrittura possono procedere simultaneamente.
  3. Le operazioni di I/O su disco tendono a essere più sequenziali utilizzando WAL.
  4. WAL utilizza un numero inferiore di operazioni fsync() ed è quindi meno vulnerabile ai problemi sui sistemi in cui la chiamata di sistema fsync() è interrotta.

Ma ci sono anche degli svantaggi:

  1. WAL normalmente richiede che il VFS supporti primitive di memoria condivisa. (Eccezione: WAL senza memoria condivisa) I VFS integrati di unix e windows supportano questo aspetto, ma i VFS di estensione di terze parti per sistemi operativi personalizzati potrebbero non supportarlo.
  2. Tutti i processi che utilizzano un database devono trovarsi sullo stesso computer host; WAL non funziona su un filesystem di rete.
  3. Le transazioni che comportano modifiche a più database ATTACCATI sono atomiche per ogni singolo database, ma non sono atomiche per tutti i database come insieme.
  4. Non è possibile modificare la dimensione della pagina dopo essere entrati in modalità WAL, né su un database vuoto, né usando VACUUM o ripristinando da un backup usando l'API di backup. Per modificare la dimensione della pagina è necessario essere in modalità rollback journal.
  5. Non è possibile aprire database WAL di sola lettura. Il processo di apertura deve avere privilegi di scrittura per "-shm" del file di memoria condivisa wal-index associato al database, se tale file esiste, oppure accedere in scrittura alla directory contenente il file del database se il file "-shm" non esiste. A partire da versione 3.22.0 (2018-01-22), è possibile aprire un file di database in modalità WAL di sola lettura se l'opzione -shm e -wal esistono già o possono essere creati o il database è immutabile.
  6. WAL potrebbe essere leggermente più lento (forse l'1% o il 2% in meno) rispetto all'approccio tradizionale del rollback-journal nelle applicazioni che eseguono principalmente letture e raramente scritture.
  7. Esiste un ulteriore sistema quasi-persistente "-wal" e "-shm" associati a ciascun database, il che può rendere SQLite meno attraente per l'uso come formato di file per applicazioni.
  8. C'è poi l'operazione aggiuntiva del checkpoint che, sebbene sia automatica per impostazione predefinita, è comunque qualcosa di cui gli sviluppatori di applicazioni devono tenere conto.
  9. Il WAL funziona meglio con transazioni di piccole dimensioni. WAL non funziona bene con transazioni molto grandi. Per le transazioni più grandi di circa 100 megabyte, le modalità tradizionali di rollback journal saranno probabilmente più veloci. Per transazioni superiori a un gigabyte, la modalità WAL può fallire con un errore di I/O o di disco pieno. Si consiglia di utilizzare una delle modalità di rollback journal per le transazioni di dimensioni superiori a qualche decina di megabyte. A partire da versione 3.11.0 (2016-02-15), la modalità WAL funziona in modo efficiente con transazioni di grandi dimensioni come la modalità rollback.

Il journal di rollback tradizionale funziona scrivendo una copia del contenuto originale invariato del database in un file di journal di rollback separato e quindi scrivendo le modifiche direttamente nel file del database. In caso di arresto anomalo o di ROLLBACK, il contenuto originale contenuto nel journal di rollback viene riprodotto nel file di database per riportarlo allo stato originale. Il COMMIT si verifica quando il journal di rollback viene cancellato.

L'approccio WAL inverte la situazione. Il contenuto originale viene conservato nel file di database e le modifiche vengono aggiunte in un file WAL separato. Un COMMIT si verifica quando un record speciale che indica un commit viene aggiunto al WAL. In questo modo, un COMMIT può avvenire senza mai scrivere sul database originale, il che consente ai lettori di continuare a operare dal database originale inalterato mentre le modifiche vengono simultaneamente appuntate nel WAL. È possibile aggiungere più transazioni alla fine di un singolo file WAL.

2.1. Checkpointing

Naturalmente, alla fine si desidera trasferire tutte le transazioni aggiunte nel file WAL nel database originale. Il trasferimento delle transazioni del file WAL nel database è chiamato "checkpoint".

Un altro modo di pensare alla differenza tra rollback e write-ahead log è che nell'approccio rollback-journal ci sono due operazioni primitive, lettura e scrittura, mentre con un write-ahead log ci sono tre operazioni primitive: lettura, scrittura e checkpoint.

Per impostazione predefinita, SQLite esegue automaticamente un checkpoint quando il file WAL raggiunge una dimensione limite di 1000 pagine. (L'opzione a tempo di compilazione SQLITE_DEFAULT_WAL_AUTOCHECKPOINT può essere utilizzata per specificare un'impostazione predefinita diversa). Le applicazioni che utilizzano il WAL non devono fare nulla affinché si verifichino questi checkpoint. Tuttavia, se lo desiderano, possono regolare la soglia di checkpoint automatico. Oppure possono disattivare i checkpoint automatici ed eseguirli nei momenti di inattività o in un thread o processo separato.

2.2. Concorrenza

Quando inizia un'operazione di lettura su un database in modalità WAL, ricorda innanzitutto la posizione dell'ultimo record di commit valido nel WAL. Questo punto viene chiamato "punto finale". Poiché il WAL può crescere e aggiungere nuovi record di commit mentre vari lettori si connettono al database, ogni lettore può potenzialmente avere il proprio punto finale. Ma per ogni particolare lettore, l'end mark rimane invariato per tutta la durata della transazione, garantendo così che una singola transazione di lettura veda solo il contenuto del database come esisteva in un singolo momento.

Quando un lettore ha bisogno di una pagina di contenuto, controlla innanzitutto il WAL per vedere se la pagina è presente e, in caso affermativo, preleva l'ultima copia della pagina presente nel WAL prima del segno di fine del lettore. Se non esiste alcuna copia della pagina nel WAL prima della fine del lettore, la pagina viene letta dal file di database originale. I lettori possono esistere in processi separati, quindi per evitare di costringere ogni lettore a scansionare l'intero WAL alla ricerca di pagine (il file WAL può crescere fino a diversi megabyte, a seconda della frequenza con cui vengono eseguiti i checkpoint), viene mantenuta in memoria condivisa una struttura di dati chiamata "wal-index", che aiuta i lettori a individuare le pagine nel WAL in modo rapido e con un minimo di I/O. Il wal-index migliora notevolmente le prestazioni dei lettori, ma l'uso della memoria condivisa significa che tutti i lettori devono essere presenti sulla stessa macchina. Questo è il motivo per cui l'implementazione del log write-ahead non funziona su un filesystem di rete.

I writer si limitano ad aggiungere nuovo contenuto alla fine del file WAL. Poiché i writer non fanno nulla che possa interferire con le azioni dei reader, writer e reader possono funzionare contemporaneamente. Tuttavia, poiché esiste un solo file WAL, può esistere un solo writer alla volta.

Un'operazione di checkpoint preleva il contenuto dal file WAL e lo trasferisce nuovamente nel file di database originale. Un checkpoint può essere eseguito in concomitanza con i lettori, ma deve fermarsi quando raggiunge una pagina del WAL che ha superato il segno di fine di qualsiasi lettore corrente. Il checkpoint deve fermarsi a quel punto perché altrimenti potrebbe sovrascrivere parte del file di database che il lettore sta utilizzando attivamente. Il checkpoint ricorda (nel wal-index) a che punto è arrivato e riprenderà a trasferire il contenuto dal WAL al database dal punto in cui si era interrotto alla prossima invocazione.

Quindi una transazione di lettura molto lunga può impedire a un checkpointer di progredire. Ma presumibilmente ogni transazione di lettura alla fine terminerà e il checkpointer sarà in grado di continuare.

Ogni volta che si verifica un'operazione di scrittura, il writer controlla l'avanzamento del checkpointer e se l'intero WAL è stato trasferito nel database e sincronizzato e se nessun lettore sta utilizzando il WAL, allora il writer riavvolgerà il WAL all'inizio e inizierà a inserire nuove transazioni all'inizio del WAL. Questo meccanismo impedisce a un file WAL di crescere senza limiti.

2.3. Considerazioni sulle prestazioni

Le transazioni di scrittura sono molto veloci perché comportano la scrittura del contenuto una sola volta (contro le due volte delle transazioni di rollback-journal) e perché le scritture sono tutte sequenziali. Inoltre, la sincronizzazione del contenuto sul disco non è necessaria, a patto che l'applicazione sia disposta a sacrificare la durata in caso di perdita di alimentazione o di riavvio del sistema. (I writer sincronizzano il WAL a ogni commit di transazione se PRAGMA synchronous è impostato su FULL, ma omettono questa sincronizzazione se PRAGMA synchronous è impostato su NORMAL).

D'altra parte, le prestazioni di lettura peggiorano con l'aumentare delle dimensioni del file WAL, poiché ogni lettore deve controllare il contenuto del file WAL e il tempo necessario per controllare il file WAL è proporzionale alle dimensioni del file WAL. Il wal-index aiuta a trovare il contenuto del file WAL molto più velocemente, ma le prestazioni diminuiscono comunque con l'aumentare delle dimensioni del file WAL. Pertanto, per mantenere buone prestazioni di lettura, è importante ridurre le dimensioni del file WAL eseguendo checkpoint a intervalli regolari.

L'esecuzione dei checkpoint richiede operazioni di sincronizzazione per evitare la possibilità di corruzione del database a seguito di una perdita di potenza o di un riavvio. Il WAL deve essere sincronizzato con la memoria persistente prima di spostare il contenuto dal WAL al database e il file del database deve essere sincronizzato prima di ripristinare il WAL. Il checkpoint richiede anche una maggiore ricerca. Il checkpoint si sforza di eseguire il maggior numero possibile di scritture sequenziali di pagine sul database (le pagine vengono trasferite dal WAL al database in ordine crescente), ma anche in questo caso ci saranno molte operazioni di ricerca intervallate dalle scritture di pagine. Questi fattori si combinano per rendere i checkpoint più lenti delle transazioni di scrittura.

La strategia predefinita è quella di consentire alle transazioni di scrittura successive di far crescere il WAL fino a quando non diventa di circa 1000 pagine, quindi di eseguire un'operazione di checkpoint per ogni COMMIT successivo fino a quando il WAL non viene riportato a dimensioni inferiori a 1000 pagine. Per impostazione predefinita, il checkpoint viene eseguito automaticamente dallo stesso thread che esegue la COMMIT che fa superare il limite di dimensione del WAL. Questo fa sì che la maggior parte delle operazioni COMMIT sia molto veloce, ma che le COMMIT occasionali (quelle che attivano un checkpoint) siano molto più lente. Se questo effetto non è desiderato, l'applicazione può disabilitare il checkpoint automatico ed eseguire i checkpoint periodici in un thread separato o in un processo separato. (Di seguito sono riportati i collegamenti ai comandi e alle interfacce per ottenere questo risultato).

Si noti che con PRAGMA synchronous impostato su NORMAL, il checkpoint è l'unica operazione che emette una barriera I/O o un'operazione di sincronizzazione (fsync() su unix o FlushFileBuffers() su windows). Se un'applicazione esegue quindi checkpoint in un thread o processo separato, il thread o processo principale che esegue le query e gli aggiornamenti del database non si bloccherà mai su un'operazione di sincronizzazione. Questo aiuta a prevenire i "latch-up" nelle applicazioni che girano su un disco occupato. Lo svantaggio di questa configurazione è che le transazioni non sono più durevoli e potrebbero tornare indietro in seguito a un'interruzione di corrente o a un hard reset.

Si noti anche che esiste un compromesso tra prestazioni medie di lettura e prestazioni medie di scrittura. Per massimizzare le prestazioni in lettura, si vuole mantenere il WAL il più piccolo possibile e quindi eseguire i checkpoint frequentemente, magari ogni COMMIT. Per massimizzare le prestazioni di scrittura, si vuole ammortizzare il costo di ogni checkpoint sul maggior numero possibile di scritture, il che significa che si vogliono eseguire i checkpoint con poca frequenza e lasciare che il WAL cresca il più possibile prima di ogni checkpoint. La decisione sulla frequenza di esecuzione dei checkpoint può quindi variare da un'applicazione all'altra, a seconda dei requisiti relativi alle prestazioni di lettura e scrittura dell'applicazione. La strategia predefinita prevede l'esecuzione di un checkpoint quando il WAL raggiunge le 1000 pagine e questa strategia sembra funzionare bene nelle applicazioni di test sulle workstation, ma altre strategie potrebbero funzionare meglio su piattaforme diverse o per carichi di lavoro diversi.

La connessione a un database SQLite ha come impostazione predefinita journal_mode=DELETE. Per convertire la modalità WAL, utilizzare il seguente pragma:

PRAGMA journal_mode=WAL;

Il pragma journal_mode restituisce una stringa che rappresenta la nuova modalità di journal. In caso di successo, il pragma restituirà la stringa "wal". Se non è stato possibile completare la conversione in WAL (ad esempio, se il VFS non supporta le primitive di memoria condivisa necessarie), la modalità di journaling rimarrà invariata e la stringa restituita dalla primitiva sarà la modalità di journaling precedente (ad esempio "delete").

3.1. Checkpoint automatico

Per impostazione predefinita, SQLite esegue automaticamente il checkpoint ogni volta che si verifica un COMMIT che causa una dimensione del file WAL pari o superiore a 1000 pagine, oppure quando si chiude l'ultima connessione a un file di database. La configurazione predefinita è destinata a funzionare bene per la maggior parte delle applicazioni. Tuttavia, i programmi che desiderano un maggiore controllo possono forzare un checkpoint utilizzando il pragma wal_checkpoint o chiamando l'interfaccia C sqlite3_wal_checkpoint(). È possibile modificare la soglia del checkpoint automatico o disabilitare completamente il checkpoint automatico utilizzando il pragma wal_autocheckpoint o chiamando l'interfaccia C sqlite3_wal_autocheckpoint(). Un programma può anche usare sqlite3_wal_hook() per registrare un callback da invocare ogni volta che una transazione si impegna nel WAL. Questo callback può quindi invocare sqlite3_wal_checkpoint() o sqlite3_wal_checkpoint_v2() in base a qualsiasi criterio ritenuto opportuno. (Il meccanismo di checkpoint automatico è implementato come un semplice wrapper attorno a sqlite3_wal_hook()).

3.2. Checkpoint avviati dall'applicazione

Un'applicazione può avviare un checkpoint utilizzando qualsiasi connessione scrivibile al database semplicemente invocando sqlite3_wal_checkpoint() o sqlite3_wal_checkpoint_v2(). Esistono tre sottotipi di checkpoint che variano per la loro aggressività: PASSIVO, COMPLETO e RESTART. Lo stile di checkpoint predefinito è PASSIVO, che esegue tutto il lavoro possibile senza interferire con altre connessioni al database e che potrebbe non essere completato se ci sono lettori o scrittori simultanei. Tutti i checkpoint avviati da sqlite3_wal_checkpoint() e dal meccanismo di checkpoint automatico sono PASSIVI. I checkpoint FULL e RESTART cercano di eseguire il checkpoint fino al completamento e possono essere avviati solo da una chiamata a sqlite3_wal_checkpoint_v2(). Per ulteriori informazioni sui checkpoint FULL e RESET, consultare la documentazione di sqlite3_wal_checkpoint_v2().

3.3. Persistenza della modalità WAL

A differenza delle altre modalità di journaling, PRAGMA journal_mode=WAL è persistente. Se un processo imposta la modalità WAL, poi chiude e riapre il database, il database tornerà in modalità WAL. Al contrario, se un processo imposta (ad esempio) PRAGMA journal_mode=TRUNCATE e poi chiude e riapre il database, questo tornerà nella modalità di rollback predefinita di DELETE anziché nella precedente impostazione TRUNCATE.

La persistenza della modalità WAL significa che le applicazioni possono essere convertite all'uso di SQLite in modalità WAL senza apportare alcuna modifica all'applicazione stessa. È sufficiente eseguire "PRAGMA journal_mode=WAL;" sui file del database utilizzando la shell della riga di comando o un'altra utility, quindi riavviare l'applicazione.

La modalità WAL journal sarà impostata su tutte le connessioni allo stesso file di database se è impostata su una connessione.

Mentre una connessione al database è aperta su un database in modalità WAL, SQLite mantiene un file di journal aggiuntivo chiamato "Write Ahead Log" o "WAL File". Il nome di questo file su disco è solitamente il nome del file del database con un'aggiunta "-wal", anche se possono essere applicate regole di denominazione diverse se SQLite è compilato con SQLITE_ENABLE_8_3_NAMES.

Il file WAL esiste per tutto il tempo in cui una connessione al database è aperta. Di solito, il file WAL viene cancellato automaticamente quando l'ultima connessione al database viene chiusa. Tuttavia, se l'ultimo processo che ha aperto il database esce senza chiudere in modo pulito la connessione al database o se si utilizza il controllo SQLITE_FCNTL_PERSIST_WALfile, è possibile che il file WAL venga mantenuto sul disco dopo che tutte le connessioni al database sono state chiuse. Il file WAL fa parte dello stato persistente del database e deve essere conservato con il database se questo viene copiato o spostato. Se un file di database viene separato dal suo file WAL, le transazioni precedentemente impegnate nel database potrebbero andare perse o il file di database potrebbe essere danneggiato. L'unico modo sicuro per rimuovere un file WAL è aprire il file di database utilizzando una delle interfacce sqlite3_open() e chiudere immediatamente il database utilizzando sqlite3_close().

Il formato dei file WAL è definito con precisione ed è multipiattaforma.

Le versioni precedenti di SQLite non potevano leggere un database in modalità WAL in sola lettura. In altre parole, per leggere un database in modalità WAL era necessario l'accesso in scrittura. Questo vincolo è stato allentato a partire da SQLite versione 3.22.0 (2018-01-22).

Nelle versioni più recenti di SQLite, un database in modalità WAL su supporti di sola lettura o un database in modalità WAL privo di permessi di scrittura può essere letto a condizione che siano soddisfatte una o più delle seguenti condizioni:

  1. Il -shm e -wal esistono già e sono leggibili
  2. Esiste il permesso di scrittura sulla directory contenente il database, in modo che il file -shm e -wal possono essere creati.
  3. La connessione al database viene aperta utilizzando il parametro immutabile della query.

Anche se è possibile aprire un database in modalità WAL di sola lettura, è buona norma convertire in PRAGMA journal_mode=DELETE prima di masterizzare l'immagine di un database SQLite su un supporto di sola lettura.

In casi normali, il nuovo contenuto viene aggiunto al file WAL fino a quando il file WAL non accumula circa 1000 pagine (e quindi ha una dimensione di circa 4 MB); a questo punto viene eseguito automaticamente un checkpoint e il file WAL viene riciclato. Il checkpoint normalmente non tronca il file WAL (a meno che non sia impostato il pragma journal_size_limit). Si limita invece a far sì che SQLite inizi a sovrascrivere il file WAL dall'inizio. Questo perché normalmente è più veloce sovrascrivere un file esistente che aggiungerlo. Quando l'ultima connessione a un database viene chiusa, questa connessione esegue un ultimo checkpoint e poi cancella il WAL e il file di memoria condivisa associato, per ripulire il disco.

Nella maggior parte dei casi, quindi, le applicazioni non devono preoccuparsi del file WAL. SQLite se ne occuperà automaticamente. Tuttavia, è possibile che SQLite si trovi in uno stato in cui il file WAL cresce senza limiti, causando un eccessivo utilizzo dello spazio su disco e rallentando la velocità delle query. I punti seguenti elencano alcuni dei modi in cui ciò può accadere e come evitarli.

  • Disabilitare il meccanismo di checkpoint automatico. Nella sua configurazione predefinita, SQLite esegue il checkpoint del file WAL al termine di qualsiasi transazione quando il file WAL è lungo più di 1000 pagine. Tuttavia, esistono opzioni a tempo di compilazione e di esecuzione che possono disabilitare o rinviare questo checkpoint automatico. Se un'applicazione disabilita il checkpoint automatico, non c'è nulla che impedisca al file WAL di crescere eccessivamente.

  • Stordimento del checkpoint. Un checkpoint può essere eseguito fino al completamento e ripristinare il file WAL solo se non ci sono altre connessioni al database che utilizzano il file WAL. Se un'altra connessione ha una transazione di lettura aperta, il checkpoint non può resettare il file WAL perché potrebbe cancellare il contenuto dal lettore. Il checkpoint farà tutto il lavoro possibile senza disturbare il lettore, ma non può essere eseguito fino al completamento. Il checkpoint riprenderà da dove si era interrotto dopo la transazione di scrittura successiva. Questa operazione si ripete fino a quando un checkpoint non è in grado di completarsi.

    Tuttavia, se un database ha molti lettori che si sovrappongono e c'è sempre almeno un lettore attivo, nessun checkpoint potrà essere completato e quindi il file WAL crescerà senza limiti.

    Questo scenario può essere evitato assicurandosi che ci siano dei "vuoti di lettura": momenti in cui nessun processo legge dal database e che i checkpoint vengano tentati in quei momenti. In applicazioni con molti lettori contemporanei, si può anche considerare l'esecuzione di checkpoint manuali con le opzioni SQLITE_CHECKPOINT_RESTART o SQLITE_CHECKPOINT_TRUNCATE, che assicurano che il checkpoint venga eseguito fino al completamento prima di tornare indietro. Lo svantaggio di utilizzare SQLITE_CHECKPOINT_RESTART e SQLITE_CHECKPOINT_TRUNCATE è che i lettori potrebbero bloccarsi durante l'esecuzione del checkpoint.

  • Transazioni di scrittura molto grandi. Un checkpoint può essere completato solo quando non ci sono altre transazioni in corso, il che significa che il file WAL non può essere ripristinato nel mezzo di una transazione di scrittura. Pertanto, una modifica importante a un database di grandi dimensioni potrebbe causare un file WAL di grandi dimensioni. Il file WAL sarà sottoposto a checkpoint una volta completata la transazione di scrittura (supponendo che non ci siano altri lettori che lo bloccano), ma nel frattempo il file può diventare molto grande.

    A partire da SQLite versione 3.11.0 (2016-02-15), il file WAL per una singola transazione deve essere di dimensioni proporzionali alla transazione stessa. Le pagine modificate dalla transazione devono essere scritte nel file WAL una sola volta. Tuttavia, con le versioni precedenti di SQLite, la stessa pagina potrebbe essere scritta nel file WAL più volte se la transazione cresce più della cache delle pagine.

Il wal-index è implementato utilizzando un file ordinario che viene mmappato per garantire la robustezza. Le prime implementazioni (prima del rilascio) della modalità WAL memorizzavano il wal-index nella memoria condivisa volatile, come i file creati in /dev/shm su Linux o /tmp su altri sistemi unix. Il problema di questo approccio è che i processi con una directory radice diversa (cambiata via chroot) vedranno file diversi e quindi utilizzeranno aree di memoria condivisa diverse, causando la corruzione del database. Altri metodi per creare blocchi di memoria condivisa senza nome non sono portabili tra le varie versioni di unix. E non siamo riusciti a trovare alcun metodo per creare blocchi di memoria condivisa senza nome su Windows. L'unico modo che abbiamo trovato per garantire che tutti i processi che accedono allo stesso file di database utilizzino la stessa memoria condivisa è quello di creare la memoria condivisa tramite la mappatura di un file nella stessa directory del database stesso.

L'uso di un normale file su disco per fornire la memoria condivisa ha lo svantaggio che potrebbe effettivamente fare dell'I/O su disco non necessario, scrivendo la memoria condivisa su disco. Tuttavia, gli sviluppatori non ritengono che questo sia un problema importante, poiché il wal-index raramente supera i 32 KiB di dimensione e non viene mai sincronizzato. Inoltre, il file di backup del wal-index viene cancellato quando l'ultima connessione al database si disconnette, il che spesso impedisce che si verifichi un vero I/O su disco.

Le applicazioni specializzate per le quali l'implementazione predefinita della memoria condivisa è inaccettabile possono ideare metodi alternativi tramite un VFS personalizzato. Per esempio, se è noto che un particolare database sarà accessibile solo da thread all'interno di un singolo processo, il wal-index può essere implementato usando la memoria heap invece della vera memoria condivisa.

Inizio in SQLite versione 3.7.4 (2010-12-07), i database WAL possono essere creati, letti e scritti anche se la memoria condivisa non è disponibile, purché il locking_mode sia impostato su EXCLUSIVE prima del primo tentativo di accesso. In altre parole, un processo può interagire con un database WAL senza utilizzare la memoria condivisa se è garantito che sarà l'unico processo ad accedere al database. Questa caratteristica consente ai database WAL di essere creati, letti e scritti da VFS legacy che non dispongono dei metodi di memoria condivisa "versione 2" xShmMap, xShmLock, xShmBarrier e xShmUnmap sull'oggetto sqlite3_io_methods.

Se la modalità di blocco EXCLUSIVE è impostata prima del primo accesso al database in modalità WAL, SQLite non tenta mai di chiamare nessuno dei metodi in memoria condivisa e quindi non viene mai creato un indice wal in memoria condivisa. In questo caso, la connessione al database rimane in modalità EXCLUSIVE finché la modalità journal è WAL; i tentativi di cambiare la modalità di chiusura usando "PRAGMA locking_mode=NORMAL;" sono nulli. L'unico modo per uscire dalla modalità di blocco EXCLUSIVE è quello di uscire prima dalla modalità journal WAL.

Se la modalità di chiusura NORMALE è in vigore per il primo accesso al database in modalità WAL, viene creato un indice wal in memoria condivisa. Ciò significa che il VFS sottostante deve supportare la memoria condivisa "versione 2". Se il VFS non supporta i metodi di memoria condivisa, il tentativo di aprire un database già in modalità WAL o di convertire un database in modalità WAL fallirà. Finché una sola connessione utilizza un indice wal a memoria condivisa, la modalità di chiusura può essere cambiata liberamente tra NORMALE ed ESCLUSIVA. Solo se il wal-index di memoria condivisa viene omesso, quando la modalità di chiusura è EXCLUSIVE prima del primo accesso al database in modalità WAL, la modalità di chiusura è bloccata in EXCLUSIVE.

Il secondo vantaggio della modalità WAL è che gli scrittori non bloccano i lettori e i lettori non bloccano gli scrittori. Questo è per lo più vero. Ma ci sono alcuni casi oscuri in cui una query contro un database in modalità WAL può restituire SQLITE_BUSY, quindi le applicazioni dovrebbero essere preparate a questa eventualità.

I casi in cui una query contro un database in modalità WAL può restituire SQLITE_BUSY sono i seguenti:

  • Se un'altra connessione di database ha la modalità di database aperta in modalità di blocco esclusivo, tutte le query contro il database restituiranno SQLITE_BUSY. Sia Chrome che Firefox aprono i loro file di database in modalità di blocco esclusivo, quindi i tentativi di leggere i database di Chrome o Firefox mentre le applicazioni sono in esecuzione incontreranno questo problema, ad esempio.

  • Quando l'ultima connessione a un particolare database viene chiusa, tale connessione acquisisce un blocco esclusivo per un breve periodo di tempo mentre pulisce i file WAL e di memoria condivisa. Se un secondo database tenta di aprire e interrogare il database mentre la prima connessione è ancora nel mezzo del processo di pulizia, la seconda connessione potrebbe ricevere un errore SQLITE_BUSY.

  • Se l'ultima connessione a un database si è bloccata, la prima nuova connessione che apre il database avvia un processo di recupero. Durante il recupero viene mantenuto un blocco esclusivo. Pertanto, se una terza connessione al database tenta di eseguire una query mentre la seconda connessione sta eseguendo il recupero, la terza connessione otterrà un errore SQLITE_BUSY.

Il formato del file di database è invariato per la modalità WAL. Tuttavia, il file WAL e il wal-index sono concetti nuovi e quindi le vecchie versioni di SQLite non sapranno come recuperare un database SQLite che si è schiantato e che stava operando in modalità WAL quando si è verificato l'arresto. Per evitare che le vecchie versioni di SQLite (precedenti alla versione 3.7.0, 2010-07-22) tentino di recuperare un database in modalità WAL (peggiorando la situazione), i numeri di versione del formato del file di database (byte 18 e 19 nell'intestazione del database) sono aumentati da 1 a 2 in modalità WAL. Pertanto, se una versione precedente di SQLite tenta di connettersi a un database SQLite che opera in modalità WAL, viene segnalato un errore del tipo "il file è criptato o non è un database".

È possibile uscire esplicitamente dalla modalità WAL utilizzando un pragma come questo:

PRAGMA journal_mode=DELETE;

Se si esce deliberatamente dalla modalità WAL, i numeri di versione del formato del file di database vengono riportati a 1, in modo che le versioni più vecchie di SQLite possano nuovamente accedere al file di database.