SQLite er en populær relationel database, som du integrerer i din applikation. Med en stigende mængde data i din database, skal du anvende SQLite-ydelsesjustering. Denne artikel diskuterer indekser og dets faldgruber, brugen af forespørgselsplanlæggeren, journaltilstanden Write-Ahead-Logging (WAL) og forøgelse af cachestørrelsen. Den uddyber også vigtigheden af at måle effekten af dine tweaks ved hjælp af automatiserede tests.
Introduktion
SQLite er et populært relationsdatabasesystem (DB) . I modsætning til dets større, klient-serverbaserede brødre, såsom MySQL, kan SQLite indlejres i din applikation som et bibliotek . SQLite har et meget lignende funktionssæt og kan også håndtere millioner af rækker, givet at du kender et par tips og tricks om performance tuning. Som de følgende afsnit vil vise, er der mere at vide om SQLite-ydelsesjustering end blot at oprette indekser.
Opret indekser, men med forsigtighed
Den grundlæggende idé med et indeks er at fremskynde læsningen af specifikke data , dvs. SELECT
udsagn med en WHERE
klausul. Indekser fremskynder også sortering data (ORDER BY
), eller JOIN
ing tabeller. Desværre er indekser et tveægget sværd, fordi de bruger ekstra diskplads, og de forsinker datamanipulation (INSERT
, UPDATE
, DELETE
).
Det generelle råd er at oprette så få indekser som muligt, men så mange som nødvendigt . Indekser giver også kun mening for større databaser med tusinder eller millioner af rækker.
Brug forespørgselsplanlæggeren til at analysere dine forespørgsler
Måden indeks bruges internt af SQLite er dokumenteret, men ikke særlig let at forstå. Som yderligere uddybet i denne artikel, er det en god idé at analysere en forespørgsel ved at sætte den foran med EXPLAIN QUERY PLAN
. Tag et kig på hver outputlinje, hvoraf der er tre grundlæggende varianter:
SEARCH table ...
linjer er et godt tegn – SQLite bruger et af dine indekser!SCAN table ... USING INDEX
er et dårligt tegn,SCAN table ...
er endnu værre!
Prøv at undgå SCAN table [using index]
poster i outputtet af EXPLAIN QUERY PLAN
når det er muligt, fordi du vil løbe ind i ydeevneproblemer på større databaser. Brug EXPLAIN QUERY PLAN
til iterativ tilføj eller rediger dine indekser, indtil der ikke er mere SCAN table
poster vises.
Optimer forespørgsler, der involverer IS NOT
Søger efter IS NOT ...
er dyrt fordi SQLite bliver nødt til at scanne alle rækker i tabellen, selvom den berørte kolonne har et indeks . Indeks er kun nyttige, hvis du leder efter specifikke værdier, dvs. sammenligninger, der involverer < (mindre), > (større), eller = (lige), men de ansøger ikke om !=(ulige).
Et smart lille trick er, at du kan erstatte WHERE column != value
med WHERE column > value OR column < value
. Dette vil bruge kolonnens indeks og effektivt påvirke alle rækker, hvis værdi ikke er lig med value
. Tilsvarende en WHERE stringColumn != ''
kan erstattes af WHERE stringColumn > ''
, fordi strenge er sorterbare. Når du anvender dette trick, skal du dog sikre dig, hvordan SQLite håndterer NULL
sammenligninger. For eksempel evaluerer SQLite NULL > ''
som FALSE
.
Hvis du bruger et sådant sammenligningstrick, er der en anden advarsel, hvis din forespørgsel indeholder WHERE
og ORDER BY
, hver med en anden kolonne:dette vil gøre forespørgslen ineffektiv igen. Hvis det er muligt, skal du bruge samme kolonne i WHERE
og ORDER BY
, eller opbyg et dækkende indeks der involverer både WHERE
og ORDER BY
kolonne.
Forbedre skrivehastigheden med Write-Ahead-Log
Write-Ahead-Logging (WAL) journaltilstand forbedrer skrive-/opdateringsydelsen markant , sammenlignet med standard tilbageføring journaltilstand. Men som dokumenteret her er der nogle få forbehold . For eksempel er WAL-tilstand ikke tilgængelig på visse operativsystemer. Der er også reducerede datakonsistensgarantier i tilfælde af hardwarefejl . Sørg for at læse de første par sider for at forstå, hvad du laver.
Jeg fandt ud af, at kommandoen PRAGMA synchronous = NORMAL
giver en 3-4x speedup. Indstilling af journal_mode
til WAL
forbedrer derefter ydeevnen betydeligt igen (ca. 10x eller mere, afhængigt af operativsystemet).
Udover de forbehold, jeg allerede har nævnt, skal du også være opmærksom på følgende:
- Ved brug af WAL-journaltilstand vil der være to yderligere filer ved siden af databasefilen på dit filsystem, som har samme navn som databasen, men med suffikset "-shm" og "-wal". Normalt behøver du ikke være ligeglad, men hvis du skulle sende databasen til en anden maskine, mens din applikation kører, så glem ikke at inkludere disse to filer. SQLite vil komprimere disse to filer til hovedfilen, når du normalt lukker alle åbne databaseforbindelser.
- Indsættelses- eller opdateringsydelsen vil af og til falde, når forespørgslen udløser sammenlægningen af WAL-logfilens indhold i hoveddatabasefilen. Dette kaldes checkpointing , se her.
- Jeg fandt den
PRAGMA
s, der ændrerjournal_mode
ogsynchronous
ser ikke ud til at være konstant gemt i databasen. Derfor har jeg altid genudfør dem, hver gang jeg åbner en ny databaseforbindelse, i stedet for blot at udføre dem, når du opretter tabellerne for første gang.
Mål alt
Når du tilføjer ydeevnejusteringer, skal du sørge for at måle effekten. Automatiske (enheds)tests er en fantastisk tilgang til dette. De hjælper med at dokumentere dine resultater for dit team, og de vil afsløre afvigende adfærd over tid , for eksempel. når du opdaterer til en nyere SQLite-version. Eksempler på hvad du kan måle:
- Hvad er effekten af at bruge WAL journaltilstand over tilbageføringen mode? Hvad er effekten af andre (angiveligt) præstationsfremmende
PRAGMA
s? - Når du tilføjer/ændrer/fjerner et indeks, hvor meget hurtigere går
SELECT
udsagn bliver? Hvor meget langsommere gårINSERT/DELETE/UPDATE
udsagn bliver? - Hvor meget ekstra diskplads bruger indekserne?
For enhver af disse tests skal du overveje at gentage dem med varierende databasestørrelser. For eksempel. køre dem på en tom database, og også på en database, der allerede indeholder tusindvis (eller millioner) af poster. Du bør også køre testene på forskellige enheder og operativsystemer, især hvis dit udviklings- og produktionsmiljø er væsentligt anderledes.
Juster cachestørrelsen
SQLite gemmer midlertidig information i en cache (i RAM), f.eks. mens du bygger resultaterne af en SELECT
forespørgsel, eller ved manipulation af data, som endnu ikke er blevet begået. Som standard er denne størrelse sølle 2 MB . Moderne stationære maskiner kan spare meget mere. Kør PRAGMA cache_size = -kibibytes
for at øge denne værdi (husk på minus tegn foran værdien!). Se her for yderligere information. Igen, mål hvilken indflydelse denne indstilling har på ydeevnen!
Brug REPLACE INTO til at oprette eller opdatere en række
Dette er måske ikke så meget en præstationsjustering, da det er et pænt lille trick. Antag, at du skal opdatere en række i tabel t
, eller opret en række, hvis den ikke eksisterer endnu. I stedet for at bruge to forespørgsler (SELECT
efterfulgt af INSERT
eller UPDATE
), brug REPLACE INTO
(officielle dokumenter).
For at dette skal virke, skal du tilføje en ekstra dummy-kolonne (f.eks. replacer
) til tabellen t
, som har en UNIQUE
begrænse. Kolonnens erklæring kunne f.eks. være ... replacer INTEGER UNIQUE ...
der er en del af din CREATE TABLE
udmelding. Brug derefter en forespørgsel som f.eks.
REPLACE INTO t (col1, col2, ..., replacer) VALUES (?,?,...,1)
Code language: SQL (Structured Query Language) (sql)
Når denne forespørgsel kører for første gang, udfører den blot en INSERT
. Når det køres anden gang, vises UNIQUE
begrænsning af replacer
kolonnen udløses, og konfliktløsningsadfærden vil få den gamle række til at blive slettet, hvilket automatisk skaber en ny. Du kan også finde den relaterede UPSERT-kommando som nyttig.
Konklusion
Når antallet af rækker i din database vokser, bliver ydelsesjusteringer en nødvendighed. Indeks er den mest almindelige løsning. De bytter forbedret tidskompleksitet med reduceret pladskompleksitet, forbedrer læsehastigheder, mens de påvirker datamodifikationsydelsen negativt. Jeg har vist, at du skal være ekstra forsigtig, når du sammenligner for ulighed i SELECT
sætninger, fordi SQLite ikke kan bruge indekser til sådanne slags sammenligninger. Jeg anbefaler generelt at bruge forespørgselsplanlæggeren der forklarer, hvad der sker internt for hver SQL-forespørgsel. Når du justerer noget, så mål virkningen!