Dwie bazy, dwa różne kształty
SQLite i PostgreSQL mówią w SQL, obie przechowują dane relacyjne i obie mogą napędzać prawdziwe aplikacje. Poza tym powstały dla różnych światów.
- SQLite to biblioteka. Działa wewnątrz procesu aplikacji i czyta z jednego pliku
.dbna dysku. Bez serwera, bez portu, bez użytkowników do skonfigurowania. - PostgreSQL to serwer. Działa jako osobny proces, nasłuchuje na porcie sieciowym, a aplikacja łączy się z nim jako klient.
Niemal każda inna różnica między nimi (współbieżność, wdrożenie, ścisłość typów, wydajność) wynika z tego jednego podziału architektonicznego. Pamiętaj o nim w dalszej części.
Architektura: w procesie kontra klient-serwer
Otwarcie bazy SQLite to otwarcie pliku:
Nie ma demona do uruchomienia, pg_hba.conf do edycji ani portu do wystawienia. Aplikacja ładuje bibliotekę SQLite, otwiera notes.db i zaczyna wykonywać zapytania. Wdrożenie to "skopiuj plik".
Postgres wygląda raczej tak:
# Uruchom serwer (raz, jako administrator):
sudo systemctl start postgresql
# Potem połącz się z aplikacji:
psql -h localhost -U alice -d mydb
Aplikacja rozmawia z osobnym procesem, zwykle przez TCP, czasem przez gniazdo Unix. Ta dodatkowa warstwa kosztuje czas konfiguracji i jedno przejście przez połączenie na każde zapytanie, ale w zamian daje dostęp sieciowy, uwierzytelnianie wielu użytkowników i prawdziwie współbieżnych piszących.
Współbieżność to najważniejsza kwestia
Zwykle to ona przesądza o wyborze. SQLite szereguje zapisy: w każdej chwili jeden piszący trzyma blokadę pliku bazy, a pozostali czekają. Odczyty mogą odbywać się równolegle (zwłaszcza w trybie WAL), ale zapisy idą jeden po drugim.
Postgres używa MVCC (multi-version concurrency control, wielowersyjnej kontroli współbieżności) i blokowania na poziomie wierszy. Wiele transakcji może jednocześnie zapisywać różne wiersze, nie blokując się nawzajem.
W praktyce:
- Blog z 50 czytelnikami na sekundę i jednym autorem, który od czasu do czasu coś pisze? SQLite wystarczy.
- Kasa w sklepie internetowym, gdzie setki użytkowników naraz aktualizują stany magazynowe? Postgres.
- Lokalna pamięć podręczna aplikacji mobilnej? SQLite, bez dyskusji.
- Backend SaaS dla wielu klientów z dziesiątkami procesów działających w tle? Postgres.
Tryb WAL (PRAGMA journal_mode = WAL;) znacznie poprawia współbieżność w SQLite, bo czytający nie blokują piszących, ale nie zmienia zasady jednego piszącego naraz.
Systemy typów: luźny kontra ścisły
Postgres jest ścisły. Kolumna zadeklarowana jako INTEGER odrzuca tekst i koniec:
-- Postgres
CREATE TABLE t (n INTEGER);
INSERT INTO t (n) VALUES ('not a number');
-- ERROR: invalid input syntax for type integer
SQLite domyślnie używa powinowactwa typów (type affinity), czyli sugestii, a nie reguły. To samo wstawienie się udaje:
Tekst siedzi w kolumnie INTEGER. SQLite zapisał go jako tekst. Ta elastyczność była świadomą decyzją projektową: przydatną przy szybkich prototypach, niebezpieczną przy długo żyjących schematach.
Nowoczesne SQLite (3.37+) obsługuje tabele STRICT, które zachowują się bardziej jak Postgres:
Jeśli zaczynasz nowy projekt na SQLite, używaj STRICT. Usuwa to całą klasę niespodzianek w stylu "skąd w mojej kolumnie liczbowej wziął się tekst".
Zakres funkcji
Postgres ma więcej prawie wszystkiego: typów danych (tablice, zakresy, typy geometryczne, sieciowe, własne enumy), języków proceduralnych (PL/pgSQL, PL/Python), wyszukiwania pełnotekstowego z rankingiem, widoków zmaterializowanych, partycjonowania tabel, replikacji, zabezpieczeń opartych na rolach i bogatego ekosystemu rozszerzeń (PostGIS, TimescaleDB, pgvector).
SQLite pokrywa podstawy i dodaje kilka przydatnych funkcji na swoją skalę: funkcje JSON, wyszukiwanie pełnotekstowe przez FTS5, indeksy R-Tree, funkcje okna, CTE, kolumny generowane. Pomija wszystko, co zakłada istnienie serwera: użytkowników, role, replikację, dostęp sieciowy.
Z grubsza:
- Potrzebujesz GIS, wyszukiwania wektorowego albo replikacji? Postgres.
- Musisz dostarczyć bazę wewnątrz aplikacji na iOS? SQLite.
- Potrzebujesz obu? Wiele zespołów rozwija i testuje na SQLite, a wdraża na Postgres, choć takie połączenie potrafi dać się we znaki przez różnice w składni (patrz niżej).
Różnice w składni, na które faktycznie trafisz
Większość codziennego SQL jest identyczna. Różnice skupiają się wokół schematu, typów i kilku wbudowanych funkcji:
-- Automatycznie rosnący klucz główny
-- SQLite:
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
-- Postgres:
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
-- albo w nowoczesnym Postgres:
CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT);
-- Bieżący znacznik czasu
-- SQLite: CURRENT_TIMESTAMP (zwraca tekst)
-- Postgres: NOW() (zwraca timestamp)
-- Typ logiczny
-- SQLite: brak prawdziwego BOOLEAN; używaj INTEGER 0/1
-- Postgres: BOOLEAN z TRUE/FALSE
Jeśli rozwijasz na SQLite, a wdrażasz na Postgres, opłaca się trzymać ORM lub narzędzie do migracji między sobą a surowym SQL. Inaczej te różnice przeciekają do aplikacji.
Wydajność, uczciwie
"Szybsze" zależy od pytania. Dla jednego procesu wykonującego odczyty i małe zapisy SQLite trudno pobić: nie ma przejścia przez sieć, parsowania protokołu ani połączenia klienta. W benchmarkach z jednym klientem SQLite często wyprzedza Postgres przy prostych zapytaniach.
Dodaj współbieżnych piszących, duże zbiory danych wymagające równoległego wykonywania zapytań albo złożone plany, które korzystają z dojrzałego planera Postgres, a Postgres obejmuje prowadzenie. Postgres skaluje się też pionowo (większe maszyny, więcej rdzeni) w sposób, do którego SQLite po prostu nie został zaprojektowany.
Uczciwe podsumowanie: SQLite jest szybki w tym, do czego służy. Postgres jest szybki w tym, do czego służy. Wybieraj według kształtu obciążenia, a nie nagłówków benchmarków.
Krótki przewodnik decyzyjny
Sięgnij po SQLite, gdy:
- Dane działają obok jednej aplikacji: desktopowej, mobilnej, wbudowanej, narzędzia CLI.
- Zapisy pochodzą z jednego procesu lub kilku procesów.
- Chcesz wdrożenia bez żadnej konfiguracji.
- Prototypujesz i chcesz skupić się na schemacie, a nie na infrastrukturze.
Sięgnij po Postgres, gdy:
- Do bazy zapisuje wiele serwerów aplikacji lub procesów roboczych.
- Potrzebujesz dostępu sieciowego z wielu klientów.
- Potrzebujesz zaawansowanych funkcji: ról, replikacji, GIS, własnych typów, procedur składowanych.
- Dane to trwały, centralny magazyn usługi produkcyjnej.
Częsta ścieżka: zacznij mały projekt na SQLite i przejdź na Postgres, jeśli i kiedy wymusi to charakter ruchu. Migracja nie jest darmowa, ale to znana operacja, a większość projektów nigdy jej nie potrzebuje.
Dalej: kiedy SQLite to właściwy wybór
Powyższe porównanie pokazuje kompromisy. Następna strona dokładniej omawia argumenty za SQLite: obciążenia, przy których jest nie tylko wystarczająco dobry, ale wręcz lepszy, oraz sygnały ostrzegawcze, że już z niego wyrastasz.
Najczęściej zadawane pytania
Jaka jest główna różnica między SQLite a Postgres?
SQLite to wbudowana biblioteka, która czyta i zapisuje jeden plik w procesie twojej aplikacji. PostgreSQL to osobny serwer, z którym łączysz się przez sieć. Ta jedna różnica architektoniczna napędza niemal każde inne porównanie: współbieżność, wdrożenie, typy i narzędzia wynikają właśnie z niej.
Czy SQLite jest szybszy niż Postgres?
Przy odczytach i małych zapisach w jednym procesie często tak: SQLite nie ma przejścia przez sieć ani narzutu protokołu klient-serwer. Przy współbieżnych zapisach od wielu klientów Postgres wychodzi na prowadzenie dzięki blokowaniu na poziomie wierszy i MVCC. To, co jest 'szybsze', zależy od obciążenia, a nie od silnika.
Czy mogę używać SQLite na produkcji?
Tak, przy odpowiednim kształcie obciążenia. SQLite bez problemu obsługuje na produkcji strony internetowe, aplikacje desktopowe i urządzenia wbudowane. Granicą są współbieżni piszący: jeśli wiele procesów musi zapisywać w tym samym czasie, Postgres obsługuje to natywnie, a SQLite szereguje zapisy. Tryb WAL pomaga, ale nie znosi tego ograniczenia.
Jak przenieść się z SQLite na Postgres?
Wyeksportuj schemat i dane przez sqlite3 mydb.db .dump, a potem popraw SQL: AUTOINCREMENT zmienia się w SERIAL lub GENERATED AS IDENTITY, zmieniają się nazwy typów, a kilka dziwactw SQLite, takich jak luźne typowanie, wymaga uporządkowania. Narzędzia takie jak pgloader automatyzują większość pracy. Przygotuj się na przepisanie wszystkiego, co opierało się na elastycznym typowaniu SQLite.