Równoczesny dostęp do SQLite

177

Czy SQLite3 bezpiecznie obsługuje równoczesny dostęp przez wiele procesów odczytujących / zapisujących z tej samej bazy danych? Czy są od tego wyjątki dotyczące platform?

anand
źródło
3
Zapomniałem wspomnieć o bramce nagród : większość odpowiedzi mówi, że wszystko jest w porządku: „SQLite jest wystarczająco szybki”, „SQLite dobrze radzi sobie z współbieżnością” itp., Ale imho, nie odpowiadaj szczegółowo / nie wyjaśniaj jasno, co się stanie, jeśli dwie operacje zapisu dotarłby dokładnie w tym samym czasie (bardzo rzadki przypadek teoretyczny). 1) Czy spowodowałoby to błąd i przerwałoby program? lub 2) Czy druga operacja zapisu zaczekałaby do zakończenia pierwszej? lub 3) Czy jedna z operacji zapisu zostałaby odrzucona (utrata danych!)? 4) Coś jeszcze? Znajomość ograniczeń współbieżnego pisania może być przydatna w wielu sytuacjach.
Basj
7
@Basj Krótko mówiąc, 2) będzie czekać i ponawiać kilka razy (konfigurowalne), 1) wywołać błąd, SQLITE_BUSY. 3) można zarejestrować wywołanie zwrotne w celu obsługi błędów SQLITE_BUSY.
obgnaw

Odpowiedzi:

112

Jeśli większość tych jednoczesnych dostępów to odczyty (np. SELECT), SQLite może je bardzo dobrze obsłużyć. Ale jeśli zaczniesz pisać jednocześnie, rywalizacja o blokady może stać się problemem. Wiele wtedy zależałoby od szybkości twojego systemu plików, ponieważ sam silnik SQLite jest niezwykle szybki i ma wiele sprytnych optymalizacji, aby zminimalizować rywalizację. Szczególnie SQLite 3.

W przypadku większości aplikacji na komputery stacjonarne / laptopy / tablety / telefony SQLite jest wystarczająco szybki, ponieważ nie ma wystarczającej współbieżności. (Firefox intensywnie używa SQLite do zakładek, historii itp.)

W przypadku aplikacji serwerowych ktoś jakiś czas temu powiedział, że wszystko poniżej 100 000 odsłon dziennie może być doskonale obsłużone przez bazę danych SQLite w typowych sytuacjach (np. Blogi, fora), a ja nie widziałem jeszcze żadnych dowodów na to, że jest inaczej. W rzeczywistości przy nowoczesnych dyskach i procesorach 95% witryn i usług internetowych działałoby dobrze z SQLite.

Jeśli chcesz naprawdę szybkiego dostępu do odczytu / zapisu, użyj bazy danych SQLite w pamięci . Pamięć RAM jest o kilka rzędów wielkości szybsza niż dysk.

kijin
źródło
16
OP nie pyta o wydajność i szybkość, ale o jednoczesny dostęp. Serwery WWW nie mają z tym nic wspólnego. To samo dotyczy bazy danych pamięci.
Jarekczek
1
Do pewnego stopnia masz rację, ale efektywność / szybkość ma znaczenie. Szybszy dostęp oznacza krótszy czas oczekiwania na blokady, zmniejszając w ten sposób wady wydajności współbieżności SQLite. W szczególności, jeśli masz niewiele i szybkich zapisów, baza danych nie wydaje się mieć żadnych problemów ze współbieżnością dla użytkownika.
Caboose,
1
jak można zarządzać jednoczesnym dostępem do bazy danych sqlite w pamięci?
P-Gn
42

Tak, SQLite dobrze radzi sobie ze współbieżnością, ale nie jest najlepszy z punktu widzenia wydajności. Z tego, co wiem, nie ma od tego wyjątków. Szczegóły znajdują się na stronie SQLite: https://www.sqlite.org/lockingv3.html

To stwierdzenie jest interesujące: "Moduł pagera zapewnia, że ​​zmiany zachodzą od razu, że albo wszystkie zmiany zachodzą, albo żadna z nich, że dwa lub więcej procesów nie próbuje uzyskać dostępu do bazy danych w niekompatybilny sposób w tym samym czasie"

vcsjones
źródło
2
Oto kilka komentarzy na temat problemów na różnych platformach , a mianowicie systemach plików NFS i Windows (chociaż może to dotyczyć tylko starszych wersji systemu Windows ...)
Nate
1
Czy jest możliwe załadowanie bazy danych SQLite3 do pamięci RAM, z której będą mogli korzystać wszyscy użytkownicy w PHP? Zgaduję, że nie, ponieważ jest to proceduralne
Anon343224user,
1
@foxyfennec .. punkt wyjścia, chociaż SQLite może nie być optymalną bazą danych w tym przypadku użycia. sqlite.org/inmemorydb.html
kingPuppy
37

Tak. Dowiedzmy się, dlaczego

SQLite jest transakcyjny

Wszystkie zmiany w ramach pojedynczej transakcji w SQLite następują w całości lub wcale

Taka obsługa ACID, jak również współbieżne odczyty / zapisy są zapewniane na 2 sposoby - za pomocą tzw. Dziennikowania (nazwijmy to „ starym sposobem ”) lub rejestrowania z wyprzedzeniem (nazwijmy to „ nowym sposobem ”)

Dziennik (dawny sposób)

W tym trybie SQLite używa blokowania DATABASE-LEVEL . To jest kluczowy punkt do zrozumienia.

Oznacza to, że kiedykolwiek musi coś przeczytać / zapisać, najpierw uzyskuje blokadę na CAŁYM pliku bazy danych. Wielu czytelników może współistnieć i czytać coś równolegle

Podczas pisania upewnia się, że uzyskano blokadę na wyłączność i żaden inny proces nie odczytuje / nie pisze jednocześnie, a zatem zapisy są bezpieczne.

Dlatego tutaj mówią, że SQlite implementuje transakcje z możliwością serializacji

Kłopoty

Ponieważ za każdym razem musi blokować całą bazę danych i wszyscy czekają na proces obsługujący współbieżność zapisu, a takie współbieżne zapisy / odczyty mają dość niską wydajność

Wycofania / przestoje

Przed zapisaniem czegoś do pliku bazy danych SQLite najpierw zapisywałby fragment do zmiany w pliku tymczasowym. Jeśli coś ulegnie awarii w trakcie zapisywania do pliku bazy danych, pobierze ten plik tymczasowy i cofnie wprowadzone w nim zmiany

Rejestrowanie z wyprzedzeniem lub WAL (nowy sposób)

W tym przypadku wszystkie zapisy są dołączane do pliku tymczasowego ( dziennik zapisu z wyprzedzeniem ) i plik ten jest okresowo łączony z oryginalną bazą danych. Kiedy SQLite czegoś szuka, najpierw sprawdza ten plik tymczasowy i jeśli nic nie zostanie znalezione, kontynuuje pracę z głównym plikiem bazy danych.

W rezultacie czytelnicy nie konkurują z pisarzami, a wydajność jest znacznie lepsza w porównaniu z Old Way.

Ostrzeżenia

SQlite w dużym stopniu zależy od podstawowej funkcji blokowania systemu plików, więc należy go używać ostrożnie, więcej szczegółów tutaj

Prawdopodobnie napotkasz również błąd w bazie danych jest zablokowany , szczególnie w trybie kronikowania, więc Twoja aplikacja musi być zaprojektowana z uwzględnieniem tego błędu

ffeast
źródło
34

Wygląda na to, że nikt nie wspomniał o trybie WAL (Write Ahead Log). Upewnij się, że transakcje są odpowiednio zorganizowane, a przy włączonym trybie WAL nie ma potrzeby blokowania bazy danych, podczas gdy ludzie czytają rzeczy podczas aktualizacji.

Jedynym problemem jest to, że w pewnym momencie WAL musi zostać ponownie włączony do głównej bazy danych i robi to po zamknięciu ostatniego połączenia z bazą danych. W przypadku bardzo obciążonej witryny może minąć kilka sekund, zanim wszystkie połączenia zostaną zamknięte, ale 100 000 odwiedzin dziennie nie powinno stanowić problemu.

akc42
źródło
Ciekawe, ale działa tylko na pojedynczym komputerze, a nie na scenariuszach, w których dostęp do bazy danych jest dostępny w sieci.
Bobík
Warto wspomnieć, że domyślny limit czasu oczekiwania pisarza to 5 sekund, a po tym database is lockedbłędzie zostanie podniesiony przez pisarza
mirhossein
16

W 2019 roku pojawiły się dwie nowe opcje jednoczesnego zapisu, które nie zostały jeszcze wydane, ale są dostępne w oddzielnych oddziałach.

„PRAGMA journal_mode = wal2”

Zaletą tego trybu dziennika nad zwykłym trybem „wal” jest to, że piszący mogą kontynuować zapisywanie do jednego pliku wal, podczas gdy drugi jest w punkcie kontrolnym.

BEGIN CONCURRENT - link do szczegółowego dokumentu

Ulepszenie BEGIN CONCURRENT umożliwia wielu programistom jednoczesne przetwarzanie transakcji zapisu, jeśli baza danych jest w trybie „wal” lub „wal2”, chociaż system nadal serializuje polecenia COMMIT.

Kiedy transakcja zapisu jest otwierana z "BEGIN CONCURRENT", w rzeczywistości blokowanie bazy danych jest odraczane do momentu wykonania polecenia COMMIT. Oznacza to, że dowolna liczba transakcji rozpoczętych za pomocą BEGIN CONCURRENT może przebiegać jednocześnie. System stosuje optymistyczne blokowanie na poziomie strony, aby zapobiec zatwierdzaniu sprzecznych współbieżnych transakcji.

Razem są obecne w begin-concurrent-wal2 lub każdy w oddzielnej własnej gałęzi .

VB
źródło
1
Czy mamy pojęcie, kiedy te funkcje pojawią się w wydanej wersji? Naprawdę mogą mi się przydać.
Peter Moore
2
Brak pomysłu. Z gałęzi można było łatwo budować. Dla .NET mam bibliotekę z niskopoziomowym interfejsem i WAL2 + rozpocznij pracę współbieżną + FTS5: github.com/Spreads/Spreads.SQLite
VB
Jasne, dzięki. Bardziej zastanawiam się nad stabilnością. SQLite jest na najwyższym poziomie, jeśli chodzi o ich wydania, ale nie wiem, jak ryzykowne byłoby użycie gałęzi w kodzie produkcyjnym.
Peter Moore
2
Zobacz ten wątek github.com/Expensify/Bedrock/issues/65 i Bedrock w ogóle. Używają go w produkcji i pchają to begin concurrent.
VB
13

SQLite ma blokadę czytelników i zapisów na poziomie bazy danych. Wiele połączeń (prawdopodobnie należących do różnych procesów) może odczytywać dane z tej samej bazy danych w tym samym czasie, ale tylko jedno może zapisywać w bazie danych.

SQLite obsługuje nieograniczoną liczbę jednoczesnych czytników, ale w danym momencie pozwala tylko jednemu pisarzowi. W wielu sytuacjach nie stanowi to problemu. Kolejka pisarzy w górę. Każda aplikacja działa szybko i działa w bazie danych, a żadna blokada nie trwa dłużej niż kilkadziesiąt milisekund. Istnieją jednak aplikacje, które wymagają większej współbieżności i mogą one wymagać szukania innego rozwiązania. - Odpowiednie zastosowania SQLite @ SQLite.org

Blokada czytnik-zapis umożliwia niezależne przetwarzanie transakcji i jest realizowana przy użyciu wyłącznych i współdzielonych blokad na poziomie bazy danych.

Blokada na wyłączność musi zostać uzyskana, zanim połączenie wykona operację zapisu w bazie danych. Po uzyskaniu blokady na wyłączność, operacje odczytu i zapisu z innych połączeń są blokowane do ponownego zwolnienia blokady.

Szczegóły implementacji w przypadku współbieżnych zapisów

SQLite ma tabelę blokad, która pomaga blokować bazę danych tak późno, jak to możliwe podczas operacji zapisu, aby zapewnić maksymalną współbieżność.

Stan początkowy to ODBLOKOWANY iw tym stanie połączenie nie uzyskało jeszcze dostępu do bazy danych. Gdy proces jest połączony z bazą danych i nawet transakcja została rozpoczęta z BEGIN, połączenie jest nadal w stanie ODBLOKOWANY.

Po stanie ODBLOKOWANE następnym stanem jest stan WSPÓLNY. Aby móc odczytać (nie zapisywać) danych z bazy danych, połączenie musi najpierw wejść w stan SHARED, uzyskując blokadę SHARED. Wiele połączeń może jednocześnie uzyskiwać i utrzymywać WSPÓLNE blokady, więc wiele połączeń może jednocześnie odczytywać dane z tej samej bazy danych. Ale tak długo, jak tylko jedna blokada SHARED pozostaje nie zwolniona, żadne połączenie nie może pomyślnie zakończyć zapisu do bazy danych.

Jeśli połączenie chce zapisywać do bazy danych, musi najpierw uzyskać blokadę ZAREZERWOWANA.

Jednocześnie może być aktywna tylko jedna ZAREZERWOWANA blokada, chociaż wiele WSPÓLNYCH blokad może współistnieć z jedną ZAREZERWOWANĄ blokadą. ZAREZERWOWANE różni się od OCZEKUJĄCEGO tym, że nowe WSPÓLNE blokady można nabyć, gdy istnieje blokada ZAREZERWOWANA. - Blokowanie plików i współbieżność w SQLite w wersji 3 @ SQLite.org

Gdy połączenie uzyska blokadę ZAREZERWOWANĄ, może rozpocząć przetwarzanie operacji modyfikacji bazy danych, chociaż te modyfikacje można wykonać tylko w buforze, a nie w rzeczywistości zapisać na dysku. Modyfikacje zawartości odczytu zapisywane są w buforze pamięci. Gdy połączenie chce złożyć modyfikację (lub transakcję), konieczne jest uaktualnienie blokady RESERVED do blokady EXCLUSIVE. Aby uzyskać zamek, musisz najpierw podnieść zamek do blokady PENDING.

Blokada PENDING oznacza, że ​​proces utrzymujący blokadę chce zapisywać do bazy danych tak szybko, jak to możliwe i po prostu czeka na wyczyszczenie wszystkich bieżących blokad SHARED, aby mógł uzyskać blokadę EXCLUSIVE. Żadne nowe blokady SHARED nie są dozwolone w bazie danych, jeśli blokada PENDING jest aktywna, chociaż istniejące blokady SHARED mogą być kontynuowane.

Do zapisu w pliku bazy danych potrzebna jest EKSKLUZYWNA blokada. Tylko jedna blokada EXCLUSIVE jest dozwolona dla pliku i żadne inne blokady jakiegokolwiek rodzaju nie mogą współistnieć z blokadą EXCLUSIVE. Aby zmaksymalizować współbieżność, SQLite działa tak, aby zminimalizować czas utrzymywania blokad EXCLUSIVE. - Blokowanie plików i współbieżność w SQLite w wersji 3 @ SQLite.org

Można więc powiedzieć, że SQLite bezpiecznie obsługuje równoczesny dostęp wielu procesów piszących do tej samej bazy danych tylko dlatego, że go nie obsługuje! Otrzymasz SQLITE_BUSYlub SQLITE_LOCKEDdla drugiego pisarza, gdy osiągnie limit ponownych prób.

obgnaw
źródło
Dziękuję Ci. Przykład kodu z dwoma autorami byłby świetny do zrozumienia, jak to działa.
Basj
1
@Basj w skrócie, sqlite mają blokadę odczytu i zapisu w pliku bazy danych, czyli to samo, co równoczesny zapis pliku. W przypadku WAL nadal nie można pisać równolegle, ale WAL może przyspieszyć pisanie, a odczyt i zapis mogą być współbieżne. WAL wprowadza nową blokadę, taką jak WAL_READ_LOCK, WAL_WRITE_LOCK.
obgnaw
2
Czy możesz użyć kolejki i mieć wiele wątków zasilających kolejkę i tylko jeden wątek zapisujący do bazy danych za pomocą instrukcji SQL w kolejce. Coś w tym stylu
Gabriel
7

Ten wątek jest stary, ale myślę, że dobrze byłoby udostępnić wyniki moich testów wykonanych na sqlite: uruchomiłem 2 wystąpienia programu w Pythonie (różne procesy ten sam program) wykonujących instrukcje SELECT i UPDATE polecenia sql w ramach transakcji z blokadą EXCLUSIVE i limitem czasu ustawionym na 10 sekund na zablokowanie, a wynik był frustrujący. Każda instancja zrobiła w pętli 10000 kroków:

  • Połącz się z db z wyłączną blokadą
  • wybierz w jednym wierszu, aby odczytać licznik
  • zaktualizuj wiersz o nową wartość równą licznikowi zwiększoną o 1
  • zamknąć połączenie z db

Nawet jeśli sqlite przyznał blokadę transakcji na wyłączność, całkowita liczba faktycznie wykonanych cykli nie była równa 20 000, ale była mniejsza (całkowita liczba iteracji na jednym liczniku liczona dla obu procesów). Program w Pythonie prawie nie zgłosił żadnego wyjątku (tylko raz podczas wyboru dla 20 wykonań). Wersja sqlite w momencie testu to 3.6.20, a Python v3.3 CentOS 6.5. Moim zdaniem lepiej jest znaleźć bardziej niezawodny produkt do tego rodzaju pracy lub ograniczyć zapisy do sqlite do jednego unikalnego procesu / wątku.

asf las
źródło
5
Wygląda na to, że musisz powiedzieć kilka magicznych słów, aby uzyskać blokadę w Pythonie, jak omówiono tutaj: stackoverflow.com/a/12848059/1048959 Pomimo faktu, że dokumentacja sqlite pythona prowadzi do przekonania, że with conto wystarczy.
Dan Stahlke