SQLite är en populär relationsdatabas som du bäddar in i din applikation. Med en ökande mängd data i din databas måste du tillämpa SQLite-prestandajustering. Den här artikeln diskuterar index och dess fallgropar, användningen av frågeplaneraren, journalläget Write-Ahead-Logging (WAL) och ökning av cachestorleken. Den utvecklar också vikten av att mäta effekten av dina justeringar med hjälp av automatiserade tester.
Introduktion
SQLite är ett populärt, relationsdatabassystem (DB) . Till skillnad från sina större, klientserverbaserade bröder, som MySQL, kan SQLite bäddas in i din applikation som ett bibliotek . SQLite har en mycket liknande funktionsuppsättning och kan också hantera miljontals rader, givet att du kan några tips och tricks om prestandajustering. Som följande avsnitt kommer att visa, finns det mer att veta om SQLite-prestandajustering än att bara skapa index.
Skapa index, men med försiktighet
Den grundläggande idén med ett index är att snabba på läsningen av specifik data , det vill säga SELECT
satser med en WHERE
klausul. Indeks påskyndar också sorteringen data (ORDER BY
), eller JOIN
ing tabeller. Tyvärr är index ett tveeggat svärd, eftersom de förbrukar ytterligare diskutrymme och de saktar ner datamanipulation (INSERT
, UPDATE
, DELETE
).
Det allmänna rådet är att skapa så få index som möjligt, men så många som behövs . Dessutom är index bara meningsfulla för större databaser, med tusentals eller miljontals rader.
Använd frågeplaneraren för att analysera dina frågor
Sättet som index används internt av SQLite är dokumenterat, men inte särskilt lätt att förstå. Som ytterligare utvecklas i den här artikeln är det en bra idé att analysera en fråga genom att sätta prefixet EXPLAIN QUERY PLAN
. Ta en titt på varje utdatarad, av vilka det finns tre grundläggande varianter:
SEARCH table ...
linjer är ett gott tecken – SQLite använder ett av dina index!SCAN table ... USING INDEX
är ett dåligt tecken,SCAN table ...
är ännu värre!
Försök att undvika SCAN table [using index]
poster i utdata från EXPLAIN QUERY PLAN
när det är möjligt, eftersom du kommer att stöta på prestandaproblem på större databaser. Använd EXPLAIN QUERY PLAN
till iterativ lägg till eller ändra dina index tills du inte längre SCAN table
poster visas.
Optimera frågor som involverar IS NOT
Kontrollerar efter IS NOT ...
är dyrt eftersom SQLite måste skanna alla rader i tabellen, även om den berörda kolumnen har ett index . Index är bara användbara om du letar efter specifika värden, d.v.s. jämförelser som involverar < (mindre), > (större), eller = (lika), men de ansöker inte om !=(ojämlika).
Ett snyggt litet knep är att du kan ersätta WHERE column != value
med WHERE column > value OR column < value
. Detta använder kolumnens index och påverkar effektivt alla rader vars värde inte är lika med value
. På liknande sätt, en WHERE stringColumn != ''
kan ersättas med WHERE stringColumn > ''
, eftersom strängar är sorterbara. När du använder det här tricket, se till att du vet hur SQLite hanterar NULL
jämförelser. Till exempel utvärderar SQLite NULL > ''
som FALSE
.
Om du använder ett sådant jämförelseknep finns det ytterligare en varning om din fråga innehåller WHERE
och ORDER BY
, var och en med olika kolumner:detta kommer att göra frågan ineffektiv igen. Om möjligt, använd samma kolumnen i WHERE
och ORDER BY
, eller bygg ett täckande index som involverar både WHERE
och ORDER BY
kolumn.
Förbättra skrivhastigheten med Write-Ahead-Log
Write-Ahead-Logging (WAL) journalläge förbättrar avsevärt skriv/uppdateringsprestanda , jämfört med standard återställning journalläge. Men som dokumenterats här finns det några varningar . Till exempel är WAL-läget inte tillgängligt på vissa operativsystem. Det finns också minskade garantier för datakonsistens i händelse av maskinvarufel . Se till att läsa de första sidorna för att förstå vad du gör.
Jag upptäckte att kommandot PRAGMA synchronous = NORMAL
ger en 3-4x speedup. Ställer in journal_mode
till WAL
förbättrar sedan prestandan avsevärt igen (ungefär 10x eller mer, beroende på operativsystem).
Förutom de varningar jag redan nämnt, bör du också vara medveten om följande:
- Med WAL-journalläget kommer det att finnas ytterligare två filer bredvid databasfilen på ditt filsystem, som har samma namn som databasen, men med suffixet "-shm" och "-wal". Normalt behöver du inte bry dig, men om du skulle skicka databasen till en annan maskin medan din applikation körs, glöm inte att inkludera dessa två filer. SQLite komprimerar dessa två filer till huvudfilen när du vanligtvis stängde alla öppna databasanslutningar.
- Prestandan för infogning eller uppdatering kommer att sjunka då och då, när frågan utlöser sammanslagning av WAL-loggfilens innehåll i huvuddatabasfilen. Detta kallas checkpointing , se här.
- Jag hittade den där
PRAGMA
s som ändrarjournal_mode
ochsynchronous
verkar inte vara permanent lagrade i databasen. Alltså, jag alltid kör dem igen när jag öppnar en ny databasanslutning, istället för att bara köra dem när du skapar tabellerna för första gången.
Mät allt
När du lägger till prestandajusteringar, se till att mäta effekten. Automatiska (enhets)tester är ett utmärkt tillvägagångssätt för detta. De hjälper till att dokumentera dina resultat för ditt team, och de kommer att avslöja avvikande beteende över tid , t.ex. när du uppdaterar till en nyare SQLite-version. Exempel på vad du kan mäta:
- Vad är effekten av att använda WAL journalläge över återställning läge? Vad är effekten av andra (förmodligen) prestationshöjande
PRAGMA
s? - När du lägger till/ändrar/tar bort ett index, hur mycket snabbare går
SELECT
uttalanden bli? Hur mycket långsammare gårINSERT/DELETE/UPDATE
uttalanden blir? - Hur mycket extra diskutrymme förbrukar indexen?
För något av dessa test, överväg att upprepa dem med olika databasstorlekar. T.ex. kör dem på en tom databas, och även på en databas som redan innehåller tusentals (eller miljoner) poster. Du bör också köra testerna på olika enheter och operativsystem, särskilt om din utvecklings- och produktionsmiljö är väsentligt annorlunda.
Justera cachestorleken
SQLite lagrar tillfällig information i en cache (i RAM-minnet), t.ex. medan du bygger resultatet av en SELECT
fråga, eller vid manipulering av data som ännu inte har registrerats. Som standard är den här storleken ynka 2 MB . Moderna stationära maskiner kan spara mycket mer. Kör PRAGMA cache_size = -kibibytes
för att öka detta värde (tänk på minus tecken framför värdet!). Se här för mer information. Återigen, mät vilken inverkan den här inställningen har på prestanda!
Använd REPLACE INTO för att skapa eller uppdatera en rad
Detta kanske inte är så mycket av en prestandajustering eftersom det är ett snyggt litet trick. Anta att du behöver uppdatera en rad i tabellen t
, eller skapa en rad om det inte finns ännu. Istället för att använda två frågor (SELECT
följt av INSERT
eller UPDATE
), använd REPLACE INTO
(officiella dokument).
För att detta ska fungera, lägg till en extra dummy-kolumn (t.ex. replacer
) till tabellen t
, som har en UNIQUE
begränsa. Kolumnens deklaration kunde t.ex. vara ... replacer INTEGER UNIQUE ...
som är en del av din CREATE TABLE
påstående. Använd sedan en fråga som t.ex.
REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)
Code language: SQL (Structured Query Language) (sql)
När den här frågan körs för första gången kommer den helt enkelt att utföra en INSERT
. När den körs andra gången visas UNIQUE
begränsning för replacer
kolumnen kommer att utlösas och konfliktlösningsbeteendet gör att den gamla raden tas bort, vilket skapar en ny automatiskt. Du kanske också tycker att det relaterade UPSERT-kommandot är användbart.
Slutsats
När antalet rader i din databas växer, blir prestandajusteringar en nödvändighet. Index är den vanligaste lösningen. De byter ut förbättrad tidskomplexitet mot minskad rymdkomplexitet, förbättrade läshastigheter, samtidigt som de påverkar datamodifieringsprestandan negativt. Jag har visat att du måste vara extra försiktig när du jämför ojämlikhet i SELECT
satser, eftersom SQLite inte kan använda index för sådana typer av jämförelser. Jag rekommenderar generellt att du använder frågeplaneraren som förklarar vad som händer internt för varje SQL-fråga. När du justerar något, mät effekten!