Accesso simultaneo SQLite


177

SQLite3 gestisce in modo sicuro l'accesso simultaneo da parte di più processi di lettura / scrittura dallo stesso DB? Ci sono delle eccezioni alla piattaforma?


3
Ho dimenticato di menzionare la bounty goall: la maggior parte delle risposte dice che va bene: "SQLite è abbastanza veloce", "SQLite gestisce bene la concorrenza" ecc. Ma, imho, non risponde in dettaglio / non spiega chiaramente cosa succede se due operazioni di scrittura arriverebbe esattamente allo stesso tempo (caso teorico molto raro). 1) Attiverebbe un errore e interromperebbe il programma? oppure 2) La seconda operazione di scrittura aspetterebbe fino al termine della prima? oppure 3) Una delle operazioni di scrittura verrebbe scartata (perdita di dati!)? 4) Qualcos'altro? Conoscere i limiti della scrittura simultanea potrebbe essere utile in molte situazioni.
Basj,

7
@Basj In breve, 2) attenderà e riproverà più volte (configurabile), 1) genererà un errore, SQLITE_BUSY.3), è possibile registrare un callback per gestire gli errori SQLITE_BUSY.
obgnaw

Risposte:


112

Se la maggior parte di questi accessi simultanei sono letti (ad es. SELECT), SQLite può gestirli molto bene. Ma se si inizia a scrivere contemporaneamente, la contesa tra i blocchi potrebbe diventare un problema. Molto dipenderà quindi da quanto è veloce il tuo filesystem, poiché lo stesso motore SQLite è estremamente veloce e ha molte ottimizzazioni intelligenti per ridurre al minimo la contesa. Soprattutto SQLite 3.

Per la maggior parte delle applicazioni desktop / laptop / tablet / telefono, SQLite è abbastanza veloce in quanto non c'è abbastanza concorrenza. (Firefox utilizza ampiamente SQLite per segnalibri, cronologia, ecc.)

Per le applicazioni server, qualcuno qualche tempo fa ha affermato che qualcosa di meno di 100.000 visualizzazioni di pagina al giorno potrebbero essere gestite perfettamente da un database SQLite in scenari tipici (ad esempio blog, forum) e non ho ancora visto alcuna prova del contrario. In effetti, con i moderni dischi e processori, il 95% dei siti Web e dei servizi Web funzionerebbe perfettamente con SQLite.

Se si desidera un accesso in lettura / scrittura molto veloce, utilizzare un database SQLite in memoria . La RAM è più veloce di molti ordini di grandezza rispetto al disco.


16
OP non chiede efficienza e velocità, ma l'accesso simultaneo. I server Web non hanno nulla a che fare con esso. Lo stesso vale per il database di memoria.
Jarekczek,

1
Hai ragione, ma l'efficienza / la velocità hanno un ruolo. Accesso più rapido significa che il tempo trascorso in attesa di blocchi è inferiore, riducendo così gli svantaggi delle prestazioni di concorrenza di SQLite. In particolare, se hai poche e veloci scritture, il DB non sembrerà avere alcun problema di concorrenza per un utente.
Caboose,

1
come gestiresti l'accesso simultaneo a un database sqlite in memoria?
P-Gn,

42

Sì, SQLite gestisce bene la concorrenza, ma non è la migliore dal punto di vista delle prestazioni. Da quello che posso dire, non ci sono eccezioni a questo. I dettagli sono sul sito di SQLite: https://www.sqlite.org/lockingv3.html

Questa affermazione è interessante: "Il modulo cercapersone garantisce che le modifiche avvengano tutte in una volta, che si verifichino tutte le modifiche o nessuna di esse, che due o più processi non provino ad accedere al database in modi incompatibili contemporaneamente"


2
Ecco alcuni commenti su problemi su piattaforme diverse , vale a dire i file system NFS e Windows (anche se potrebbe riguardare solo le vecchie versioni di Windows ...)
Nate,

1
È possibile caricare un database SQLite3 nella RAM da utilizzare per tutti gli utenti in PHP? Immagino di no, essendo procedurale
Anon343224utente

1
@foxyfennec .. un punto di partenza, anche se SQLite potrebbe non essere il db ottimale per questo caso d'uso. sqlite.org/inmemorydb.html
kingPuppy

37

Sì lo fa. Scopriamo perché

SQLite è transazionale

Tutte le modifiche all'interno di una singola transazione in SQLite si verificano completamente o per niente

Tale supporto ACID e le letture / scritture simultanee sono fornite in 2 modi - usando il cosiddetto journaling (chiamiamolo " vecchio modo ") o logging write-ahead (chiamiamolo " nuovo modo ")

Journaling (vecchio modo)

In questa modalità SQLite utilizza il blocco DATABASE-LEVEL . Questo è il punto cruciale da capire.

Ciò significa che ogni volta che deve leggere / scrivere qualcosa acquisisce prima un blocco sul file di database INTERO . Più lettori possono coesistere e leggere qualcosa in parallelo

Durante la scrittura si assicura che venga acquisito un blocco esclusivo e che nessun altro processo sia in lettura / scrittura simultanea e che quindi le scritture siano sicure.

Ecco perché qui stanno dicendo strumenti SQlite transazioni serializzabili

Troubles

Poiché deve bloccare un intero database ogni volta e tutti aspettano che un processo di gestione risulti compromesso dalla scrittura e tali scritture / letture simultanee hanno prestazioni piuttosto basse

Rollback / interruzioni

Prima di scrivere qualcosa nel file di database, SQLite salverebbe innanzitutto il blocco da modificare in un file temporaneo. Se qualcosa si interrompe durante la scrittura nel file di database, questo file temporaneo viene prelevato e ripristinato le modifiche

Scrittura anticipata o WAL (nuovo modo)

In questo caso, tutte le scritture vengono aggiunte a un file temporaneo ( registro write-ahead ) e questo file viene periodicamente unito al database originale. Quando SQLite è alla ricerca di qualcosa, controlla prima questo file temporaneo e se non viene trovato nulla, procedi con il file di database principale.

Di conseguenza, i lettori non competono con gli scrittori e le prestazioni sono molto migliori rispetto al vecchio modo.

Avvertenze

SQlite dipende fortemente dalla funzionalità di blocco del filesystem sottostante, quindi dovrebbe essere usato con cautela, maggiori dettagli qui

È anche probabile che si verifichi un errore di blocco del database , specialmente nella modalità journaling, quindi l'app deve essere progettata tenendo presente questo errore


35

Nessuno sembra aver menzionato la modalità WAL (Write Ahead Log). Assicurarsi che le transazioni siano organizzate correttamente e con la modalità WAL impostata, non è necessario mantenere bloccato il database mentre le persone leggono le cose mentre è in corso un aggiornamento.

L'unico problema è che a un certo punto il WAL deve essere nuovamente incorporato nel database principale e lo fa alla chiusura dell'ultima connessione al database. Con un sito molto affollato potresti trovare alcuni secondi per chiudere tutte le connessioni, ma 100K accessi al giorno non dovrebbero essere un problema.


Interessante, ma funziona solo su un singolo computer, non su scenari in cui si accede al database sulla rete.
Bobík,

Vale la pena ricordare che il timeout predefinito per uno scrittore di attendere è di 5 secondi e dopo che l' database is lockederrore verrà generato dallo scrittore
mirhossein,

16

Nel 2019, ci sono due nuove opzioni di scrittura simultanee non ancora rilasciate ma disponibili in rami separati.

"PRAGMA journal_mode = wal2"

Il vantaggio di questa modalità journal rispetto alla normale modalità "wal" è che gli scrittori possono continuare a scrivere su un file wal mentre l'altro è checkpoint.

INIZIA CONCORRENTE - link al documento dettagliato

Il miglioramento BEGIN CONCURRENT consente a più writer di elaborare le transazioni di scrittura contemporaneamente se il database è in modalità "wal" o "wal2", sebbene il sistema continui a serializzare i comandi COMMIT.

Quando viene aperta una transazione di scrittura con "BEGIN CONCURRENT", il blocco effettivo del database viene rinviato fino all'esecuzione di un COMMIT. Ciò significa che qualsiasi numero di transazioni avviate con BEGIN CONCURRENT può procedere contemporaneamente. Il sistema utilizza il blocco ottimistico a livello di pagina per impedire il commit di transazioni simultanee in conflitto.

Insieme sono presenti in begin-concorrente-wal2 o ciascuno in un proprio ramo separato .


1
Abbiamo idea di quando queste funzionalità entreranno nella versione di rilascio? Potrebbero davvero tornare utili per me.
Peter Moore,

2
Nessuna idea. Potresti costruire facilmente dai rami. Per .NET ho una libreria con interfaccia di basso livello e WAL2 + inizio simultaneo + FTS5: github.com/Spreads/Spreads.SQLite
VB

Oh, certo grazie. Mi chiedo di più sulla stabilità. Il primato di SQLite è di prim'ordine quando si tratta delle loro versioni, ma non so quanto rischioso sarebbe usare un ramo nel codice di produzione.
Peter Moore,

2
Vedi questa discussione github.com/Expensify/Bedrock/issues/65 e Bedrock in generale. Lo usano in produzione e hanno spinto quella begin concurrentroba.
VB

13

SQLite ha un blocco lettori-scrittore a livello di database. Connessioni multiple (possibilmente di proprietà di processi diversi) possono leggere i dati dallo stesso database contemporaneamente, ma solo uno può scrivere nel database.

SQLite supporta un numero illimitato di lettori simultanei, ma consentirà un solo writer in qualsiasi momento nel tempo. Per molte situazioni, questo non è un problema. Writer in coda. Ogni applicazione fa in modo che il suo database funzioni rapidamente e vada avanti e nessun blocco dura più di qualche decina di millisecondi. Ma ci sono alcune applicazioni che richiedono più concorrenza e quelle applicazioni potrebbero aver bisogno di cercare una soluzione diversa. - Usi appropriati per SQLite @ SQLite.org

Il blocco lettori-scrittore consente l'elaborazione di transazioni indipendenti ed è implementato utilizzando blocchi esclusivi e condivisi a livello di database.

È necessario ottenere un blocco esclusivo prima che una connessione esegua un'operazione di scrittura su un database. Dopo aver ottenuto il blocco esclusivo, entrambe le operazioni di lettura e scrittura da altre connessioni vengono bloccate fino al rilascio del blocco.

Dettagli di implementazione per il caso di scritture simultanee

SQLite ha una tabella di blocco che aiuta a bloccare il database il più tardi possibile durante un'operazione di scrittura per garantire la massima concorrenza.

Lo stato iniziale è SBLOCCATO e in questo stato la connessione non ha ancora effettuato l'accesso al database. Quando un processo è collegato a un database e anche una transazione è stata avviata con INIZIA, la connessione è ancora nello stato SBLOCCATO.

Dopo lo stato SBLOCCATO, lo stato successivo è lo stato CONDIVISO. Per poter leggere (non scrivere) i dati dal database, la connessione deve prima entrare nello stato CONDIVISO, ottenendo un blocco CONDIVISO. Connessioni multiple possono ottenere e gestire blocchi SHARED contemporaneamente, quindi più connessioni possono leggere i dati dallo stesso database contemporaneamente. Fintanto che anche un solo blocco CONDIVISO rimane inedito, nessuna connessione può completare con successo una scrittura nel database.

Se una connessione desidera scrivere nel database, deve prima ottenere un blocco RISERVATO.

È possibile attivare solo un singolo blocco RISERVATO alla volta, sebbene più blocchi CONDIVISI possano coesistere con un singolo blocco RISERVATO. RISERVATO differisce da IN ATTESA in quanto è possibile acquisire nuovi blocchi CONDIVISI mentre è presente un blocco RISERVATO. - Blocco dei file e concorrenza in SQLite versione 3 @ SQLite.org

Una volta che una connessione ottiene un blocco RISERVATO, può iniziare a elaborare le operazioni di modifica del database, sebbene queste modifiche possano essere eseguite solo nel buffer, piuttosto che effettivamente scritte sul disco. Le modifiche apportate al contenuto della lettura vengono salvate nel buffer di memoria. Quando una connessione desidera inviare una modifica (o transazione), è necessario aggiornare il blocco RISERVATO a un blocco ESCLUSIVO. Per ottenere il blocco, è necessario innanzitutto sollevare il blocco su un blocco IN ATTESA.

Un blocco PENDING significa che il processo che tiene il blocco vuole scrivere nel database il più presto possibile e sta solo aspettando che tutti i blocchi SHARED attuali vengano cancellati in modo che possa ottenere un blocco ESCLUSIVO. Non sono consentiti nuovi blocchi condivisi sul database se è attivo un blocco PENDING, sebbene i blocchi condivisi esistenti possano continuare.

È necessario un blocco ESCLUSIVO per scrivere nel file di database. È consentito un solo blocco ESCLUSIVO sul file e nessun altro blocco di alcun tipo può coesistere con un blocco ESCLUSIVO. Al fine di massimizzare la concorrenza, SQLite lavora per ridurre al minimo il tempo di permanenza dei blocchi EXCLUSIVE. - Blocco dei file e concorrenza in SQLite versione 3 @ SQLite.org

Quindi potresti dire che SQLite gestisce in modo sicuro l'accesso simultaneo da più processi che scrivono sullo stesso DB semplicemente perché non lo supporta! Otterrai SQLITE_BUSYo SQLITE_LOCKEDper il secondo autore quando raggiungerà la limitazione dei tentativi.


Grazie. Un esempio di codice con 2 autori sarebbe fantastico per capire come funziona.
Basj,

2
@Basj in breve, sqlite ha un blocco di lettura / scrittura sul file del database. È lo stesso che scrivere contemporaneamente un file. E con WAL, non è ancora possibile scrivere contemporaneamente, ma WAL può accelerare la scrittura e leggere e scrivere possono essere simultanee. E WAL introduce un nuovo blocco come WAL_READ_LOCK, WAL_WRITE_LOCK.
obgnaw

2
È possibile utilizzare una coda e fare in modo che più thread alimentino la coda e solo un thread che scriva nel DB utilizzando le istruzioni SQL nella coda. Qualcosa di simile a questa
Gabriel

Per WAL - vedi sqlite.org/wal.html
Todd

7

Questo thread è vecchio ma penso che sarebbe utile condividere il risultato dei miei test eseguiti su sqlite: ho eseguito 2 istanze di programma Python (diversi processi stesso programma) eseguendo istruzioni SELECT e UPDATE comandi sql all'interno della transazione con blocco ESCLUSIVO e timeout impostati su 10 secondi per ottenere un blocco e il risultato è stato frustrante. Ogni istanza ha fatto in 10000 step loop:

  • connettersi a db con blocco esclusivo
  • selezionare su una riga per leggere il contatore
  • aggiorna la riga con un nuovo valore uguale al contatore incrementato di 1
  • stretta connessione a db

Anche se sqlite ha concesso il blocco esclusivo della transazione, il numero totale di cicli realmente eseguiti non era pari a 20.000 ma inferiore (il numero totale di iterazioni su un singolo contatore è stato conteggiato per entrambi i processi). Il programma Python non ha quasi generato alcuna eccezione (solo una volta durante la selezione per 20 esecuzioni). la revisione sqlite al momento del test era 3.6.20 e python v3.3 CentOS 6.5. Secondo me è meglio trovare un prodotto più affidabile per questo tipo di lavoro o limitare le scritture a sqlite a un singolo processo / thread unico.


5
Sembra che tu abbia bisogno di dire alcune parole magiche per ottenere un blocco in Python, come discusso qui: stackoverflow.com/a/12848059/1048959 Questo nonostante il fatto che la documentazione sqlite di Python ti faccia credere che with consia abbastanza.
Dan Stahlke,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.