# SQLite

## Hva det er

SQLite er en SQL-databasemotor som kjører inne i programmet som bruker den, som
et bibliotek, uten serverprosess, nettverksport eller administrator, og som
holder tabellene, indeksene, triggerne og visningene sine samlet i én vanlig
fil på disk. Navnet dukker også opp som `sqlite3`, som er navnet på
kommandolinjeskallet og på den ene C-kildefilen prosjektet leverer, og
prefikset hver funksjon i C-grensesnittet bærer. Prosjektets egen
oppsummering er at motoren er selvstendig, serverløs, uten konfigurasjon og
transaksjonell, og prosjektet ber om å bli sammenlignet med `fopen()` heller enn
med Oracle. Det SQLite erstatter, er den hjemmesnekrede datafilen, og
databaseserveren får stå i fred.

Prosjektet startet 9. mai 2000, og koden er allemannseie (public domain), så det
finnes ingen lisens å klarere før den bygges inn i et produkt. Filformatet er en
del av avtalen. En database skrevet på en 32-biters maskin kan kopieres til en
64-biters, eller mellom prosessorer med ulik byterekkefølge, big-endian og
little-endian, og åpnes uendret. Utviklerne sier at de har tenkt å støtte SQLite
fram til år 2050, og det amerikanske Library of Congress oppfører filene som et
anbefalt format for å bevare data. Med alle funksjoner slått på kan biblioteket
være mindre enn 900 KiB, og derfor sitter det i telefoner, biler, klokker og
skrivebordsprogrammer så vel som på servere.

Innvendig er det en komplett SQL-motor: sammenkoblinger, underspørringer,
visninger, triggere og transaksjoner som forblir atomiske, konsistente,
isolerte og varige selv når strømmen går midt i en skriving. To designvalg
skiller den fra serverdatabaser. Det første er samtidighet. Et hvilket som helst
antall tilkoblinger kan lese på en gang, men bare én kan skrive i et gitt
øyeblikk, og låsen dekker hele filen. Det andre er typing. Den deklarerte typen
til en kolonne er en preferanse, som SQLite kaller affinitet, og typen tilhører
egentlig hver enkelt verdi, så en kolonne deklarert som `INTEGER` godtar tekst
som ikke ligner et tall.

Mot alternativene er grensen tydelig. PostgreSQL, MySQL og SQL Server er delte
tjenester som mange maskiner snakker med over et nettverk. SQLite er et privat
lager for én applikasjon på én vert. Mot CSV- eller JSON-filer legger den til et
spørrespråk, indekser, begrensninger og transaksjoner, til prisen av én
avhengighet som de fleste programmeringsspråk allerede har med.

## Hvorfor det er viktig

For en stor del av programvaren er det vanskelige spørsmålet om en database
hvem som drifter den, mer enn hvor rask den er. En server må installeres,
oppgraderes, sikres, overvåkes og sikkerhetskopieres, og noen må få betalt for
å gjøre alt det. SQLite fjerner hele den kategorien. Å åpne en database er å
åpne en fil, en sikkerhetskopi er en kopi av den filen, tatt gjennom
backup-API-et mens den er i bruk, og en testsuite kan gi hver test sin egen
ferske database.

Den endrer også hva en fil kan være. Et program som ellers ville funnet opp et
format, skrevet en parser for det og håpet at et krasj aldri traff midt i en
lagring, kan holde tilstanden sin i SQLite og få atomiske lagringer gratis.
Prosjektet nevner denne bruken direkte: som filformat for applikasjoner, som
beholder for å flytte data mellom systemer, der mottakeren kjører SQL i stedet
for å skrive en parser, og som kladdedatabase for å sortere og oppsummere data
som kom i en annen form.

Beslutningen som teller, er smal, og den kan sjekkes før en eneste kodelinje er
skrevet: hvor mange prosesser skriver, og på hvor mange maskiner? Ett program på
én vert som skriver i korte støt, er hjemmebanen til SQLite, og mange nettsteder
med lav til middels trafikk kjører komfortabelt på den. Flere servere som
skriver til de samme dataene, eller en last der mange brukere skriver hele
tiden, hører hjemme i en klient-server-database. Størrelsen avgjør det sjelden,
siden én fil kan holde opptil 281 terabyte. Antallet skrivere gjør det.

Selv på hjemmebanen er det kostnader å veie. `ALTER TABLE` støtter bare å gi nytt
navn til en tabell eller en kolonne og å legge til eller fjerne en kolonne, så
de fleste andre endringer i formen på en tabell betyr å bygge en ny tabell,
kopiere radene over og bytte navnene. Fremmednøkler er slått av til hver
tilkobling slår dem på. Og løs typing slipper dårlige data inn i det stille,
med mindre tabellen er deklarert til å avvise dem.

## Slik ser det ut i praksis

SQLite fyller tre ulike roller i tre prosjekter her: som sannhetskilde, som
inndata til et bygg og som et sted å se på resultater.

Det første er en generator for CV-er, referanselister, porteføljer og
søknadsbrev.
[Kilden er en SQLite-database med 23 tabeller](/nb/portfolio/built-a-database-driven-cv-and-portfolio-generator-144/),
fra resultater og selskaper til profiler og referanser, satt sammen av en
pipeline i 19 trinn som går fra en tom fil til en komplett database med én
kommando. Hvert trinn er trygt å gjenta, så pipelinen kan kjøres mot en
eksisterende database uten å duplisere en rad. Førtien Python-moduler, 10 580
linjer i alt, gjengir seks utdataformater fra den ene filen, med nøyaktig to
avhengigheter under kjøring. Et dokument rettet mot én type leser er en spørring
over resultatene, valgt av en profil, og aldri en redigert kopi. Siden er ærlig
om prisen: systemet er nå den eneste måten å produsere et dokument på.

Det andre er porteføljen på dette nettstedet, som genereres fra den samme
databasen.
[En eksportør åpner SQLite-filen skrivebeskyttet og skriver 495 sider](/nb/portfolio/generated-321-achievement-pages-from-a-sqlite-export-132/)
på tre språk, bare med Pythons standardbibliotek, og utdataene sjekkes inn,
slik at nettstedet bygger uten generatorens miljø. Sideadressene lagres som
rader, én per adresse per resultat, så en omskrevet setning flytter ikke lenger
en side. Det samme arbeidet dokumenterte fellen som beskrives i neste del: en
sammenligning av perioder mot en tekstkolonne traff alle trettito radene.

Det tredje er det minste og det mest typiske. En pipeline som gjorde rå
strømavlesninger om til forbruk per time og sone, skrev resultatene enten som
CSV eller
[inn i en lettvekts SQLite-database for inspeksjon](/nb/portfolio/engineered-hourly-electricity-consumption-aggregation-pipeline-in-python-4/),
og Python-versjonen behandlet ett døgn med data på 34 ms og tretti døgn på
180 ms. En database brukt på den måten trenger ikke noe oppsett i det hele tatt:
alle som har `sqlite3`-skallet, kan åpne filen og spørre mot den.

## Hvor det går galt

Det meste av trøbbelet med SQLite kommer av å behandle den som en liten
PostgreSQL. Den er en annen maskin med andre kanter.

**Et filter på årstallet som treffer hver rad.** SQLite konverterer en verdi før
en sammenligning bare når en av sidene har en affinitet. Et funksjonsresultat
som `substr()` har ingen, og det har heller ikke et nakent tall, så SQLite
sammenligner teksten `'2019'` med heltallet `2025` slik de står, og i
sorteringsrekkefølgen kommer hvert tall før hver tekst:

```sql
CREATE TABLE stint (period TEXT, hours TEXT);
INSERT INTO stint VALUES
  ('2019-03', '9'), ('2024-11', '10'), ('2026-01', '42');

SELECT count(*) FROM stint
WHERE substr(period, 1, 4) >= 2025;                   -- 3

SELECT count(*) FROM stint
WHERE CAST(substr(period, 1, 4) AS INTEGER) >= 2025;  -- 1
```

Bruk `CAST` eksplisitt overalt der tekst møter et tall. I nye tabeller bør du
deklarere ekte typer og legge til nøkkelordet `STRICT`, som finnes fra versjon
3.37.0. En streng `INTEGER`-kolonne godtar fortsatt `'42'` og lagrer det som et
tall, men avviser `'ten'` med en feil i stedet for å lagre verdien.

**Skrivinger som begynner å feile med `database is locked`.** Én tilkobling
holder skrivelåsen, og en ny skriver enten venter eller gir opp med en gang.
Årsaken er som regel en lang transaksjon, eller en leser i standardmodusen med
tilbakerullingsjournal som holder igjen en skriver. Sett databasen over på
write-ahead logging (WAL), der lesere og skriveren slutter å blokkere hverandre,
hold skrivetransaksjonene korte, og sett en ventetid for opptatt database (busy
timeout), slik at en skriver venter litt i stedet for å feile. Er skriverne
virkelig mange og konstante, er det signalet om å flytte til en server.

**En fil som blir korrupt på en delt disk.** SQLite er avhengig av fillåsene i
operativsystemet, og mange nettverksfilsystemer har feil i hvordan de
implementerer disse låsene, så to maskiner kan ende opp med å skrive til den
samme delen av filen samtidig. WAL virker overhodet ikke over et
nettverksfilsystem, fordi hver prosess som bruker databasen, må dele minne på én
vert. Hold databasen på en disk som er lokal for det ene programmet som skriver
til den, og sett en server foran den hvis andre maskiner trenger dataene.

**En rad som overlever forelderen den peker på.** Fremmednøkler deklareres i
skjemaet, men forblir inaktive til hver tilkobling kjører
`PRAGMA foreign_keys = ON`, så en sletting i en tilkobling som hoppet over
pragmaen, etterlater foreldreløse rader uten et ord. Sett pragmaen der
applikasjonen åpner en tilkobling, på det ene stedet som gjør det, og test at
den er på.

## Les mer

- [About SQLite](https://sqlite.org/about.html)
  — prosjektets egen beskrivelse: i prosessen, serverløs, allemannseie, én fil
  som virker på tvers av plattformer.
- [Datatypes In SQLite](https://sqlite.org/datatype3.html)
  — lagringsklasser, typeaffinitet og sammenligningsreglene bak fellen over.
- [STRICT Tables](https://sqlite.org/stricttables.html)
  — hvordan du får en tabell til å håndheve de deklarerte kolonnetypene.
- [Write-Ahead Logging](https://sqlite.org/wal.html)
  — lesere og en skriver parallelt, og hvorfor det holder seg på én vert.
- [Appropriate Uses For SQLite](https://sqlite.org/whentouse.html)
  — hvor den passer, hvor en klient-server-database passer bedre, og en
  sjekkliste.
- [SQLite Foreign Key Support](https://sqlite.org/foreignkeys.html)
  — hvorfor begrensningene er av som standard, og hvordan du slår dem på.

<https://engineer.company/nb/glossary/sqlite/>
