SQLite to wbudowana relacyjna baza danych, która działa bez osobnego procesu serwerowego i przechowuje całą bazę w jednym pliku na urządzeniu. Dzięki zerowej konfiguracji, niewielkiemu rozmiarowi biblioteki i pełnemu wsparciu SQL, SQLite stała się standardem lokalnego przechowywania danych w aplikacjach mobilnych. Według danych SQLite Consortium (2025), ta SGBD jest używana w ponad 4 miliardach urządzeń, w tym w każdym smartfonie na iOS i Android.
Najważniejsze
SQLite to biblioteka w języku C implementująca relacyjną SGBD bez wydzielonego serwera. Jest wbudowywana bezpośrednio w aplikację, odczytuje i zapisuje dane w zwykłym pliku w systemie plików urządzenia. Rozmiar biblioteki wynosi około 600 KB, co czyni SQLite najlżejszą w pełni funkcjonalną bazą SQL.
SQLite obsługuje większą część standardu SQL:1999, w tym JOIN, podzapytania, triggery, widoki, indeksy i funkcje okienne. Ograniczenia dotyczą ALTER TABLE (ograniczone wsparcie) i pełnych RIGHT/FULL OUTER JOIN. Niemniej jednak dla aplikacji mobilnych funkcjonalność SQLite jest wystarczająca w 99% przypadków lokalnego przechowywania.
Według ankiety wśród programistów Stack Overflow (2025), SQLite jest najpopularniejszą bazą danych dla rozwiązań wbudowanych i zajmuje trzecie miejsce pod względem popularności wśród wszystkich SGBD po MySQL i PostgreSQL. W programowaniu mobilnym SQLite jest używana w każdej aplikacji — bezpośrednio lub przez nakładki.
Zero-configuration — SQLite nie wymaga instalacji, konfiguracji uprawnień, tworzenia użytkowników ani uruchamiania usługi. Biblioteka jest podłączana do projektu, a baza danych jest tworzona przez wywołanie jednej funkcji. To radykalnie upraszcza wdrożenie w porównaniu z SGBD klient-serwer, gdzie wymagana jest instalacja serwera, konfiguracja portów i użytkowników.
Plik bazy danych SQLite to zwykły międzyplatformowy plik, który można skopiować, przeanalizować, wysłać przez sieć lub przywrócić z kopii zapasowej. Format pliku jest stabilny na poziomie API: pliki SQLite 3 utworzone w 2004 roku otwierają się w bieżącej wersji biblioteki, co gwarantuje długoterminową kompatybilność danych.
Architektura SQLite składa się z ośmiu maszyn wirtualnych: Tokenizer, Parser, Code Generator, VM, B-Tree, Pager, OS Interface i Utilities. Zapytanie SQL przechodzi przez Tokenizer (podział na tokeny), Parser (budowa AST), Code Generator (konwersja na kod bajtowy) i jest wykonywane na maszynie wirtualnej, która odczytuje strony danych przez B-Tree i Pager.
SQLite używa B-Tree do przechowywania tabel i indeksów. Każda tabela jest przechowywana jako osobne B-Tree, gdzie węzły liści zawierają wiersze danych. Indeksy również są przechowywane jako B-Tree, ale z kluczami w liściach. Pager zarządza ładowaniem stron (domyślnie 4096 bajtów) z pliku do pamięci, zapewniając transakcje ACID poprzez dziennik lub WAL.
WAL (Write-Ahead Logging) — zalecany tryb dla aplikacji mobilnych. Zmiany są najpierw zapisywane do osobnego pliku WAL, a następnie okresowo przenoszone do głównej bazy danych. WAL umożliwia jednoczesne odczytywanie z bazy (stare dane) i zapisywanie do niej (przez WAL), co zwiększa wydajność aplikacji wielowątkowych. Standardowy dziennik (rollback journal) blokuje odczyt podczas zapisu.
| Parametr | Rollback Journal | WAL (Write-Ahead Logging) |
|---|---|---|
| Odczyt podczas zapisu | Blokowany | Dozwolony (odczytuje stare dane) |
| Wydajność zapisu | Średnia | Wysoka (sekwencyjny zapis do WAL) |
| Zużycie dysku | Mniejsze (tylko dziennik wycofania) | Większe (WAL + główna baza danych) |
| Odzyskiwanie po awarii | Wycofanie do ostatniego punktu kontrolnego | Odzyskiwanie z WAL (dane nie są tracone) |
| Zalecenie | Dla scenariuszy jednowątkowych | Dla typowych aplikacji mobilnych |
Przełączanie między trybami wykonuje się jednym zapytaniem SQL: PRAGMA journal_mode=WAL. Dla aplikacji mobilnych z synchronizacją w tle i wątkiem UI jednocześnie odczytującym dane, WAL zapewnia lepszą wydajność i brak blokowania interfejsu.
SQLite to nie jedyna opcja lokalnego przechowywania danych, ale najbardziej uniwersalna. Realm oferuje wyższą szybkość bezpośredniego dostępu do obiektów w pamięci, ale używa własnego formatu NoSQL i ma większy rozmiar biblioteki. Core Data na iOS to warstwa ORM na SQLite, która dodaje zarządzanie grafem obiektów i cofanie operacji.
Dla większości aplikacji SQLite pozostaje optymalnym wyborem dzięki przewidywalnej wydajności, zerowemu uzależnieniu od dostawcy i sprawdzonej stabilności. Realm i Core Data są uzasadnione w projektach ze złożonymi grafami obiektów, reaktywnymi zapytaniami lub wymaganiem synchronizacji między urządzeniami.
| Cecha | SQLite | Realm | Core Data |
|---|---|---|---|
| Typ bazy danych | Relacyjna (SQL) | NoSQL (obiektowa) | ORM (na SQLite) |
| Rozmiar biblioteki | ~600 KB | ~4 MB | Wbudowany w SDK Apple |
| Wydajność | Średnia | Wysoka (obiekty w pamięci) | Średnia (narzut ORM) |
| Platformy | iOS, Android, Web, Desktop | iOS, Android, Node.js | iOS, macOS |
| Uzależnienie od dostawcy | Brak (otwarty standard) | Średnie (własny format) | Wysokie (tylko Apple) |
Wybór między SQLite, Realm i Core Data zależy od platformy, wymagań dotyczących modelu obiektowego i strategii synchronizacji. Dla projektów międzyplatformowych (KMP, Flutter) SQLite pozostaje jedynym uniwersalnym wyborem działającym na wszystkich docelowych platformach bez zmian w modelu danych.
Room — biblioteka z Android Jetpack, zapewniająca warstwę ORM na SQLite. Room automatycznie generuje zapytania SQL z adnotowanych interfejsów DAO, sprawdza poprawność zapytań na etapie kompilacji i obsługuje migracje bazy danych przy zmianie schematu. Room to zalecany sposób pracy z SQLite na Android.
SQLiteOpenHelper — niskopoziomowe API do bezpośredniego zarządzania SQLite bez ORM. Klasa zarządza tworzeniem, otwieraniem i aktualizowaniem bazy danych. SQLiteOpenHelper nadaje się do projektów z prostymi zapytaniami SQL lub gdy potrzebna jest pełna kontrola nad logiką SQL bez abstrakcji Room.
Encja w Room jest adnotowana @Entity, a DAO — @Dao. Room tłumaczy adnotowane metody na zapytania SQL: @Insert generuje INSERT, @Query — SELECT z podanym SQL. Migracje są dodawane przez Migration z określeniem starej i nowej wersji schematu. Room sprawdza SQL na etapie kompilacji, co eliminuje błędy składniowe w produkcji.
@Entity
data class User(
@PrimaryKey val id: Long,
val name: String,
@ColumnInfo(name = "created_at")
val createdAt: Long
)
@Dao
interface UserDao {
@Query("SELECT * FROM user ORDER BY name ASC")
suspend fun getAllUsers(): List<User>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("DELETE FROM user WHERE id = :id")
suspend fun deleteUser(id: Long)
}
Room automatycznie generuje implementację UserDao_Impl, która zawiera zapytania w czasie wykonania do SQLite przez wewnętrzny RoomDatabase. Dzięki corutinom (suspend) metody DAO są wykonywane asynchronicznie w wątku tła, nie blokując UI. Typy zwracane Flow w @Query automatycznie aktualizują wynik przy zmianie tabeli.
FMDB — nakładka Objective-C na C API SQLite, historycznie pierwsza popularna biblioteka dla iOS. Udostępnia obiekty FMDatabase i FMResultSet do wykonywania zapytań i pobierania wyników. FMDB jest prosta i minimalistyczna, ale nie obsługuje konstrukcji specyficznych dla Swift — opcjonali, Codable, async/await.
GRDB — nowoczesna biblioteka Swift do pracy z SQLite. Zapewnia type-safe API, obsługę Codable, Combine Publishers, async/await, migracje i obserwację zmian w czasie rzeczywistym. GRDB jest preferowana dla nowych projektów w Swift dzięki pełnej integracji ze Swift Concurrency i lepszej czytelności kodu.
GRDB definiuje tabele przez klasy Record, zgodne z protokołami FetchableRecord i TableRecord. Zapytania są pisane w Swift z type-safe składnią, a nie surowym SQL. GRDB obsługuje również DatabaseMigrator do wersjonowania schematu i migracji między wersjami aplikacji.
struct User: Codable, FetchableRecord, TableRecord {
var id: Int64
var name: String
var createdAt: Date
}
let dbPool = try DatabasePool(path: dbPath)
var migrator = DatabaseMigrator()
migrator.registerMigration("v1") { db in
try db.create(table: "user") { t in
t.autoIncrementedPrimaryKey("id")
t.column("name", .text).notNull()
t.column("createdAt", .datetime).notNull()
}
}
let users = try await dbPool.read { db in
try User.order(Column("name")).fetchAll(db)
}
DatabasePool używa trybu WAL SQLite do współbieżnego odczytu. Wielu czytelników może jednocześnie uzyskiwać dostęp do bazy, podczas gdy jeden pisarz aktualizuje dane przez WAL. GRDB automatycznie zarządza połączeniami i transakcjami, zapewniając thread-safe dostęp do bazy z dowolnego wątku bez ręcznej synchronizacji.
Indeksy — najskuteczniejszy sposób przyspieszenia zapytań SQLite. Indeks jest tworzony na kolumnach uczestniczących w WHERE, JOIN i ORDER BY. Dla tabeli ze 100 000 rekordów wyszukiwanie po indeksowanej kolumnie zajmuje milisekundy zamiast sekund. Jednak indeksy spowalniają INSERT i UPDATE, dlatego ich liczba powinna być zbilansowana z częstotliwością zapisu.
Wstawianie wsadowe (batch insert) w ramach jednej transakcji radykalnie przyspiesza masowe ładowanie danych. Wstawienie 1000 rekordów pojedynczo daje narzut około 1 sekundy. Te same 1000 rekordów w jednej transakcji — około 5–10 milisekund. Różnica wynika z tego, że każdy osobny INSERT tworzy nową transakcję z synchronicznym zapisem na dysk.
PRAGMA — polecenia SQLite do konfiguracji zachowania biblioteki. Kluczowe pragmy optymalizacyjne: PRAGMA synchronous=NORMAL (zmniejsza częstotliwość fsync), PRAGMA cache_size=-8000 (przydziela 8 MB pamięci podręcznej), PRAGMA temp_store=MEMORY (tabele tymczasowe w pamięci). Dla aplikacji mobilnych z dużymi ilościami danych kombinacja tych pragm przyspiesza zapytania 2–3 razy.
Kolejną ważną optymalizacją jest wstępna kompilacja zapytań SQL (prepared statements). Jeśli zapytanie jest wykonywane wielokrotnie (na przykład wstawianie 10 000 wierszy), kompilacja SQL raz, a następnie użycie statement zmniejsza obciążenie CPU o 30–50%. Room i GRDB automatycznie buforują prepared statements, ale przy bezpośrednim użyciu C API SQLite kompilację trzeba wykonać ręcznie.
class UserRepository(private val db: RoomDatabase) {
suspend fun insertBatch(users: List<User>) {
db.withTransaction {
users.chunked(500).forEach { batch ->
batch.forEach { user ->
insertUser(user)
}
}
}
}
}
Wstawianie wsadowe z withTransaction gwarantuje, że wszystkie INSERT są wykonywane w ramach jednej transakcji. Podział na podbatch (chunked) zapobiega zbyt dużemu rozmiarowi jednej transakcji, która mogłaby zablokować inne wątki na dłuższy czas. Dla synchronizacji w tle rozmiar podbatcha 500 rekordów daje optymalną równowagę szybkości i responsywności UI.
Często zadawane pytania
Tak, SQLite obsługuje dostęp wielowątkowy w trybie WAL. Wiele wątków może jednocześnie odczytywać dane, ale zapisywać może tylko jeden. Room i GRDB zarządzają synchronizacją automatycznie. W trybie rollback journal (domyślnym) baza jest całkowicie blokowana przy każdym zapisie.
Ograniczenie SQLite — 281 TB (teoretyczne maksimum). W praktyce rozmiar bazy jest ograniczony dostępną pamięcią urządzenia. Dla aplikacji mobilnych komfortowy rozmiar to do 1–2 GB. Bazy większe niż 2 GB spowalniają tworzenie kopii zapasowej, aktualizację przez App Store i zwiększają zużycie pamięci RAM.
SQLite nie szyfruje danych domyślnie — każdy proces z dostępem do pliku może je odczytać. Do szyfrowania używaj SQLCipher (rozszerzenie z AES-256), Room z EncryptedDatabase (Android) lub Encrypted Core Data na iOS. Szyfrowanie dodaje 5–15% narzutu na odczyt i zapis danych.
SQLite to wbudowana (embedded) biblioteka, nie wymagająca procesu serwerowego. MySQL to SGBD klient-serwer z osobnym serwerem, użytkownikami, prawami dostępu i protokołem sieciowym. SQLite przechowuje bazę w jednym pliku, MySQL — w wielu plikach zarządzanych przez serwer. SQLite jest prostsza i lżejsza, MySQL potężniejsza i bardziej skalowalna.
Do migracji SQLite używaj ALTER TABLE (dodawanie kolumn) lub tworzenia nowej tabeli z przeniesieniem danych i usunięciem starej. Room automatyzuje ten proces przez klasy Migration: podaj startVersion, endVersion i zapytania SQL do zmiany schematu. GRDB i FMDB zapewniają podobne DatabaseMigrator.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również