Błędy to po prostu SQLite, który coś ci mówi
Komunikaty błędów SQLite są krótkie i czasem zagadkowe, ale sprowadzają się do niewielkiego zestawu problemów. Większość tego, co spotkasz na produkcji, mieści się w pięciu kategoriach: blokady, uprawnienia, uszkodzenia, niezgodności schematu i naruszenia ograniczeń. Ta strona omawia każdą z nich: co ją wywołuje, co naprawdę oznacza i jak ją naprawić.
Komunikatom towarzyszą kody liczbowe (kody rozszerzone są jeszcze dokładniejsze). W logach zobaczysz obie formy:
Error: database is locked -- kod 5 (SQLITE_BUSY)
Error: unable to open database -- kod 14 (SQLITE_CANTOPEN)
Error: attempt to write a readonly -- kod 8 (SQLITE_READONLY)
Error: database disk image is -- kod 11 (SQLITE_CORRUPT)
Znajomość kodu pomaga przy wyszukiwaniu: SQLITE_BUSY daje znacznie lepsze wyniki niż zwykły angielski komunikat.
database is locked (SQLITE_BUSY)
Najczęstszy błąd SQLite w każdej aplikacji, która zapisuje dane z więcej niż jednego miejsca. SQLite wykonuje zapisy po kolei: blokadę zapisu może w danej chwili trzymać tylko jedno połączenie. Jeśli drugi zapisujący nie zdobędzie blokady w czasie busy timeout, dostajesz ten błąd.
Trzy rozwiązania, od największego wpływu:
Sam tryb WAL rozwiązuje problem blokad dla większości obciążeń. Busy timeout to siatka bezpieczeństwa na wypadek rzeczywistej rywalizacji o zapis. Poza ustawieniami przejrzyj kod: transakcja zostawiona otwarta, gdy program wykonuje operacje sieciowe, trzyma blokadę przez cały ten czas. Utrzymuj krótkie transakcje i wykonuj COMMIT (lub ROLLBACK), gdy tylko praca jest skończona.
unable to open database file (SQLITE_CANTOPEN)
SQLite próbował otworzyć plik, a system operacyjny odmówił. W 95% przypadków problemem jest ścieżka pliku lub jego katalog:
-- Co sprawdzić:
-- 1. Czy ścieżka istnieje? ls -l /path/to/db.sqlite
-- 2. Czy istnieje katalog nadrzędny? SQLite tworzy plik,
-- ale nie katalog, w którym leży.
-- 3. Czy użytkownik, na którym działa proces, ma prawa
-- odczytu i zapisu do katalogu (nie tylko do pliku)?
-- 4. Czy wolumin jest zamontowany, niepełny i nie tylko do odczytu?
Subtelny przypadek: SQLite musi tworzyć obok bazy pliki pomocnicze (-journal, -wal, -shm). Jeśli sam plik jest zapisywalny, ale katalog nie, otwieranie się udaje, a zapisy nie. Zawsze nadawaj prawo zapisu na poziomie katalogu.
attempt to write a readonly database (SQLITE_READONLY)
Bliski krewny poprzedniego błędu. Plik otworzył się bez problemu, ale zapisy się nie udają. Przyczyny, od najczęstszej:
- Użytkownik systemu nie ma prawa zapisu do pliku lub jego katalogu.
- Połączenie zostało otwarte z flagą tylko do odczytu (
SQLITE_OPEN_READONLYalbomode=row URI). - Wolumin jest zamontowany tylko do odczytu (częste przy bind mountach w Dockerze i niektórych chmurowych systemach plików).
- Baza leży na sieciowym systemie plików, który nie obsługuje blokad potrzebnych SQLite.
Popraw uprawnienia albo zamontuj wolumin ponownie. W Dockerze upewnij się, że bind mount nie ma :ro i że użytkownik kontenera jest właścicielem katalogu.
database disk image is malformed (SQLITE_CORRUPT)
Bajty pliku nie pasują już do formatu SQLite. Prawdziwe przyczyny zwykle leżą w środowisku: procesy zabite w trakcie zapisu na systemach plików bez prawidłowego fsync, kopiowanie bazy podczas aktywnego zapisu, awarie sprzętu albo synchronizacja pliku przez Dropbox/iCloud.
Najpierw potwierdź uszkodzenie:
Jeśli integrity_check zwraca ok, baza jest w porządku, a błąd przyszedł skądinąd (często z nieaktualnego połączenia). Jeśli zwraca listę problemów, trzeba odzyskać dane.
Najczystsza ścieżka odzyskiwania to polecenie .recover w CLI, które wyciąga wszystkie dane, jakie się da, do nowej bazy:
sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"
Jeśli masz świeżą kopię zapasową, przywróć ją: to szybsze i pozwala uniknąć niepewności typu "odzyskaliśmy większość". Na stronie o kopiach zapasowych i przywracaniu znajdziesz właściwy sposób kopiowania działającej bazy (podpowiedź: nie cp).
no such table i no such column
Znaczą dokładnie to, co mówią, ale przyczyna to zwykle jedna z dwóch rzeczy: jesteś połączony z inną bazą, niż myślisz, albo migracja się nie wykonała.
Sprawdź connection string swojej aplikacji: ścieżki względne są liczone od bieżącego katalogu roboczego, który jest inny w terminalu, w IDE i w procesie produkcyjnym. Baza w pamięci (:memory:) jest za każdym razem nowa, co zaskakuje osoby oczekujące trwałości.
Liczy się też cytowanie identyfikatorów. Nazwy bez cudzysłowów nie rozróżniają wielkości liter, ale "User" i "user" to różne identyfikatory. Jeśli tabela została utworzona z nazwą w cudzysłowach, trzeba ją dalej cytować.
Naruszenia ograniczeń
SQLite odrzuca zapisy, które złamałyby ograniczenie. Komunikat błędu mówi, które:
Pod spodem każdy błąd ma inny kod (SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL). Rozwiązanie prawie zawsze leży w warstwie aplikacji: waliduj dane przed zapisem albo używaj INSERT ... ON CONFLICT, aby świadomie obsługiwać duplikaty.
FOREIGN KEY constraint failed zasługuje na osobną uwagę: klucze obce są w SQLite domyślnie wyłączone. Jeśli ich nie włączysz, błędne odwołania wchodzą po cichu, a potem wybuchają, gdy w końcu włączysz egzekwowanie. Ustawiaj tę pragmę w każdym połączeniu:
cannot start a transaction within a transaction
Wywołano BEGIN, gdy transakcja była już otwarta. SQLite nie pozwala na zagnieżdżone transakcje, ale pozwala na zagnieżdżone savepointy, które dają ten sam efekt:
Jeśli transakcjami zarządza twój ORM lub framework, prawdopodobnie kazano mu rozpocząć transakcję dwa razy. Sprawdź, czy autocommit jest włączony i czy pula połączeń nie używa ponownie połączenia, które ma już otwartą transakcję.
disk I/O error (SQLITE_IOERR)
System operacyjny odrzucił odczyt lub zapis. Pełny dysk, czkawka sieciowego systemu plików albo plik usunięty spod nóg SQLite. Pierwsze, co warto sprawdzić, to df -h. Drugie: czy baza nie leży na czymś niestabilnym, jak NFS albo folder synchronizowany z chmurą. SQLite zakłada lokalny system plików POSIX z działającym fsync. Jeśli nie możesz jej przenieść, pogódź się z większym ryzykiem uszkodzenia.
syntax error near "..."
Parser SQLite mówi, który token go zmylił. Poprawka zwykle leży trzy linie przed miejscem wskazanym przez błąd: brakujący przecinek, identyfikator bez cudzysłowów kolidujący ze słowem kluczowym albo tekst z apostrofami, które trzeba zapisać podwójnie ('it''s', a nie 'it's').
Do danych od użytkownika używaj wiązania parametrów (symboli ?) zamiast budowania SQL przez sklejanie ciągów. Unikniesz w ten sposób całej kategorii błędów składni i jednocześnie SQL injection.
Lista kontrolna diagnostyki
Gdy coś psuje się na produkcji, ta sekwencja obejmuje większość przypadków w mniej niż minutę:
Pięć pragm, pięć odpowiedzi. Razem z kodem błędu z nieudanego zapytania wiesz, do której kategorii należy problem i którą stronę dokumentacji otworzyć jako następną.
Podsumowanie kursu
To już cała wycieczka. Za tobą droga od CREATE TABLE przez złączenia, indeksy, transakcje, tryb WAL i kopie zapasowe aż do awarii, które pojawiają się, gdy SQLite zderza się z prawdziwym światem. Wzorce się powtarzają: krótkie transakcje, włączone klucze obce, tryb WAL, regularne kopie zapasowe i zdrowy szacunek dla PRAGMA integrity_check. Trzymaj się tych nawyków, a SQLite będzie po cichu działać latami.
Najczęściej zadawane pytania
Dlaczego SQLite zgłasza 'database is locked'?
Inne połączenie trzyma blokadę zapisu, a twoje przekroczyło czas oczekiwania. Typowe rozwiązania to włączenie trybu WAL przez PRAGMA journal_mode=WAL, aby czytający nie blokowali zapisujących, zwiększenie czasu oczekiwania przez PRAGMA busy_timeout = 5000 i pilnowanie, żeby transakcje były szybko zatwierdzane, a nie zostawiane otwarte.
Jak naprawić 'attempt to write a readonly database' w SQLite?
Prawie zawsze to problem z uprawnieniami w systemie plików, a nie z SQLite. Użytkownik systemu, na którym działa twój proces, potrzebuje prawa zapisu zarówno do pliku bazy, jak i do katalogu, w którym leży (SQLite tworzy tam pliki pomocnicze -journal lub -wal). Sprawdź właściciela, bity uprawnień i to, czy wolumin nie jest zamontowany tylko do odczytu.
Co oznacza 'database disk image is malformed'?
SQLite odczytał bajty niezgodne z oczekiwanym formatem, zwykle z powodu uszkodzenia wywołanego przez zabite procesy, wadliwe dyski albo kopiowanie pliku, gdy był otwarty. Uruchom PRAGMA integrity_check, aby to potwierdzić, a potem odzyskaj dane poleceniem .recover w CLI, które zrzuci to, co da się uratować, do nowej bazy. Jeśli masz kopię zapasową, przywrócenie jej jest szybsze.
Dlaczego dostaję 'no such table' lub 'no such column'?
Jesteś połączony z innym plikiem bazy, niż myślisz, albo migracja się nie wykonała. Sprawdź PRAGMA database_list, aby zobaczyć ścieżkę pliku, który SQLite naprawdę otworzył, i .schema tablename, aby zobaczyć rzeczywiste kolumny. Częste są też literówki i niezgodna wielkość liter w identyfikatorach: SQLite nie rozróżnia wielkości liter w nazwach bez cudzysłowów, ale w nazwach w cudzysłowach już tak.