Dodaję własną odpowiedź, ponieważ nie zgadzam się z aktualnie przyjętą. Stwierdza, że operacja nie jest bezpieczna dla wątków, ale jest to niewłaściwe - SQLite uses file locking odpowiednie dla jej obecnej platformy, aby zapewnić, że wszystkie dostępy są zgodne z ACID.
W systemach Unix będzie to blokada fcntl()
lub flock()
, która jest blokadą na jeden plik. W wyniku tego, za każdym razem, kod, który tworzy nowe połączenie, zawsze przydziela nowy uchwyt pliku, a zatem samo blokowanie SQLite zapobiegnie uszkodzeniu bazy danych. Konsekwencją tego jest to, że zazwyczaj nie jest dobrym pomysłem korzystanie z SQLite na udziale NFS lub podobnym, ponieważ często nie zapewniają one szczególnie niezawodnego blokowania (zależy to jednak od implementacji NFS).
Jak już zauważył @abernert w komentarzach, SQLite has had issues with threads, ale było to związane z dzieleniem pojedynczego połączenia między wątkami. Jak również wspomina, oznacza to, że jeśli używasz całej puli aplikacji, otrzymasz błędy środowiska wykonawczego, jeśli drugi wątek wyciągnie z puli przetworzone połączenie. Są to również irytujące błędy, których możesz nie zauważyć podczas testowania (lekkie ładowanie, może tylko jeden wątek w użyciu), ale które mogą łatwo powodować bóle głowy później. Późniejsza sugestia Martijna Pietersa dotycząca puli wątków lokalnych powinna działać dobrze.
Jak podkreślono w SQLite FAQ od wersji 3.3.1 to rzeczywiście bezpieczne przekazać połączenia pomiędzy wątkami, o ile nie posiadają zamki - było to ustępstwo, że autor SQLite dodany pomimo krytyczny ogólne wykorzystanie wątków.Każda sensowna implementacja łączenia puli zawsze zapewni, że wszystko zostało zatwierdzone lub wycofane przed zamianą połączenia w puli, więc faktycznie globalna pula aplikacji będzie prawdopodobnie prawdopodobnie bezpieczna, jeśli nie będzie to sprawdzanie Pythona przed udostępnieniem , który, jak sądzę, pozostaje na miejscu, nawet jeśli używana jest nowsza wersja SQLite. Z pewnością mój system Python 2.7.3 ma moduł sqlite3
z sqlite_version_info
raportowaniem 3.7.9, ale nadal wysyła RuntimeError
, jeśli uzyskujesz dostęp do niego z wielu wątków.
W każdym razie, dopóki istnieje kontrola, połączenia nie mogą być skutecznie udostępniane, nawet jeśli biblioteka bazowa obsługuje je.
Co do oryginalnego pytania, z pewnością tworzenie nowego połączenia za każdym razem jest mniej efektywne niż utrzymywanie puli połączeń, ale już zostało wspomniane, że musi to być lokalny wątek, co jest niewielkim problemem . Narzut tworzenia nowego połączenia z bazą danych zasadniczo otwiera plik i czyta nagłówek, aby upewnić się, że jest to poprawny plik SQLite. Narzut faktycznego wykonywania instrukcji jest wyższy, ponieważ wymaga wyodrębnienia wyglądu i wykonania dość wielu operacji wejścia/wyjścia pliku, więc większość pracy jest faktycznie odroczona do wykonania instrukcji i/lub zatwierdzenia.
Co ciekawe, przynajmniej w systemach Linux przyjrzałem się kodowi do wykonania instrukcji powtarza kroki czytania nagłówka pliku - w rezultacie otwarcie nowego połączenia nie będzie wcale takie złe ponieważ początkowy odczyt podczas otwierania połączenia spowoduje wyciągnięcie nagłówka do pamięci podręcznej systemu plików systemu. Sprowadza się to do otwarcia jednego uchwytu pliku.
Należy również dodać, że jeśli spodziewasz się skalowania kodu do wysokiej współbieżności, to SQLite może być złym wyborem. Jako their own website points out nie jest ona odpowiednia dla wysokiej współbieżności, ponieważ uderzenie wydajnościowe polegające na konieczności wyciśnięcia całego dostępu przez pojedynczą globalną blokadę zaczyna bić, gdy rośnie liczba współbieżnych wątków. W porządku, jeśli używasz wątków dla wygody, ale jeśli naprawdę oczekujesz wysokiego stopnia współbieżności, to uniknęłbym SQLite.
Podsumowując, nie uważam, że twoje podejście do otwierania za każdym razem jest tak naprawdę złe. Czy pula lokalna wątku może poprawić wydajność? Prawdopodobnie tak. Czy ten wzrost wydajności byłby zauważalny? Moim zdaniem, chyba że widzisz dość wysokie współczynniki połączeń, i na tym etapie będziesz mieć wiele wątków, więc prawdopodobnie i tak będziesz chciał odejść od SQLite, ponieważ nie radzi sobie z tym strasznie dobrze. Jeśli zdecydujesz się użyć jednego z nich, upewnij się, że czyści ono połączenie przed zwróceniem go do puli - SQLAlchemy ma pewną funkcjonalność connection pooling, która może Ci się przydać, nawet jeśli nie chcesz, aby wszystkie warstwy ORM były na wierzchu.
EDIT
Jako całkiem rozsądnie wskazał, należy załączyć rzeczywiste czasy. Te są z dość niskiego zasilania VPS:
>>> timeit.timeit("cur = conn.cursor(); cur.execute('UPDATE foo SET name=\"x\"
WHERE id=3'); conn.commit()", setup="import sqlite3;
conn = sqlite3.connect('./testdb')", number=100000)
5.733098030090332
>>> timeit.timeit("conn = sqlite3.connect('./testdb'); cur = conn.cursor();
cur.execute('UPDATE foo SET name=\"x\" WHERE id=3'); conn.commit()",
setup="import sqlite3", number=100000)
16.518677949905396
Możesz zobaczyć współczynnik około 3x różnicy, która nie jest bez znaczenia. Jednak czas bezwzględny to nadal mniej niż jedna milisekunda, więc jeśli nie wykonasz wielu zapytań na żądanie, prawdopodobnie najpierw inne miejsca do optymalizacji. Jeśli wykonujesz wiele zapytań, rozsądnym kompromisem może być nowe połączenie na żądanie (ale bez złożoności puli, po prostu podłączaj się ponownie za każdym razem).
Dla odczytu (tj. SELECT), względny narzut łączenia za każdym razem będzie wyższy, ale absolutny narzut w czasie zegara ściennego powinien być stały.
Jak już omówiono w innym miejscu tego pytania, powinieneś testować z prawdziwymi pytaniami, po prostu chciałem udokumentować, co zrobiłem, aby dojść do moich wniosków.
Proszę zaksięgować rzeczywisty kod, z właściwymi funkcjami, takimi jak 'connect' i' fetchall' zamiast 'open' i' findall'. – abarnert
Jakie są "problemy z różnymi bitami kodu"? Jeśli próbujesz wielu jednoczesnych zapisów, użyj prawdziwego RDBMS http://www.sqlite.org/faq.html#q5 – msw