Når vi arbeider med Ruby on Rails-rammeverket, arbeider vi vanligvis med relasjonsdatabaser som MySQL eller PostgreSQL. Når vi definerer migreringer ved hjelp av Active Record Migrations, støter vi på såkalte indekser, men nybegynnere forstår ofte ikke helt hva indekser er og hvilke fordeler de gir.
Når vi arbeider med Ruby on Rails-rammeverket, arbeider vi vanligvis med relasjonsdatabaser som MySQL eller PostgreSQL. Når vi definerer migreringer ved hjelp av Active Record Migrations, støter vi på såkalte indekser, men nybegynnere forstår ofte ikke helt hva indekser er og hvilke fordeler de gir.
I dette innlegget vil jeg forklare hva indekser er, hva de brukes til, og presentere noen gode fremgangsmåter for hvordan du kan bruke dem.
Database
Det finnes mange databasemotorer, og en av de mest populære er de tidligere nevnte MySQL, PostgreSQL, Oracle eller Microsoft SQL Server. De er alle relasjonsdatabaser, noe som betyr at alle data er relatert til hverandre og lagret i tabeller. Hver tabellrad kalles en post, og hver post har sin egen unike identifikator (id). Du kan sjekke rangeringen av de mest populære databasemotorene på https://db-engines.com/en/ranking. Der finner du også noen ikke-relasjonelle databaser, for eksempel MongoDB.
Opprette en indeks
Tabellene i databasene våre kan ha alt fra noen få til flere titalls - i ekstreme tilfeller opptil flere hundre - kolonner. Husk at hver tabell kan ha et ubegrenset antall rader. Dette antallet følger ikke direkte av databasens struktur, og vi bør alltid anta at antallet poster vil øke suksessivt, og at databasen følgelig vil vokse. De første antagelsene og spørringene som er skrevet i eksisterende applikasjoner, kan være gode for et lite eller middels antall poster, men over tid, når det kommer flere data, vil applikasjonens kommunikasjon med databasen ikke lenger være effektiv.
Programmererens rolle er å skrive spørringer for å hente data fra tabellen eller tabellene, men den optimale måten å behandle spørringen på avhenger av databasemotoren. Husk at databasemotorer laster inn data fra disken til minnet og deretter skanner dem. Det betyr at hvis mange brukere utfører komplekse operasjoner samtidig, vil flere av dem måtte vente på tur på grunn av mangel på ressurser til å gjennomføre søkene. Det er derfor relevante indekser er så viktige.
Wiki: Indeks - en datastruktur som gjør det raskere å utføre søkeoperasjoner i en tabell.
For hver indeks må vi definere nøkler (for én eller flere kolonner) som skal brukes til å søke etter poster i tabellen. Dataene i indeksen vil bli sortert etter nøkkelen som er definert på forhånd, noe som vil gjøre det betydelig raskere å søke etter data i tabellen. Det enkleste eksemplet fra hverdagen er en telefonkatalog der personene er sortert etter for- og etternavn. Man kan si at indeksen vår i dette tilfellet vil være for- og etternavn.
Hvordan velger du den beste indeksnøkkelen? Det er ikke vanskelig - bare husk noen få regler. Opprett en indeks basert på kolonner som:
- vil ofte bli brukt i våre henvendelser (WHERE),
- i kombinasjon med hverandre gir en unik verdi (dvs. en verdi som angir nøyaktig én rad),
- vil bli brukt som såkalte koblingskolonner (JOIN),
- gir de mest selektive nøklene, dvs. de som gir færrest antall linjer når du skriver en spørring.
Hvis vi allerede vet hvilke nøkler som vil være optimale for tabellen vår, kan vi også spørre oss selv hvor mange indekser vi trenger. I dette tilfellet er det best å vite hvilke spørringer som vil referere til tabellen vår allerede på designstadiet.
La oss lage indekser for spesifikke spørsmål som vil vises, men ikke skriv dem for hver kolonne. Indekser, som tabeller, må lagres et sted, så når vi oppretter tabeller med en indeks for hver kolonne, må vi ta hensyn til at mengden plass som brukes kan øke betydelig.
Opprett unik indeks
Et annet spørsmål vi må tenke på, er unikhet. Det er verdt å bruke fem minutter ekstra på å tenke over om indeksen vår virkelig er unik. På denne måten forteller vi søkeoptimaliseringen at den ikke trenger å forvente duplikater i spørringen. For eksempel e-postadresser:
frozenstringliteral: true
class CreateUsers < ActiveRecord::Migration[6.0]
def change
createtable :users do |t|
t.string :email, null: false
end
addindex :users, :email, unique: true
end
end
På eksemplet med PostgreSQL-motoren vil jeg vise forskjellen i spørringshastighet på e-postkolonnen med en unik indeks og uten en indeks.
1. Du kan bruke eksempel kode på din egen database for å kunne teste eksempelet nedenfor. La oss først opprette en tom tabell med én kolonne:
CREATE TABLE users (
email varchar
);
2. La oss generere 10 000 poster for testen:
DO $
BEGIN FOR i IN 1..10000 LOOP
INSERT INTO users values((select 'user' || i || '@example.com'));
END LOOP; END;
$;
Vi vil bruke EXPLAIN ANALYZE til å sjekke hvor raskt spørringen vår vil bli behandlet når vi vil finne en bestemt bruker i databasen.
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Vår spørring tvang oss til å iterere rundt i hele tabellen på jakt etter den posten vi var interessert i.
Denne prosessen kalles sekvensiell skanning. I dette tilfellet er den beste måten å gjøre jobben på å lese hele tabellen og filtrere ut bestemte rader.
PostgreSQL vil filtrere ut de unødvendige linjene og bare returnere de som interesserer oss. Dette er virkelig det beste å gjøre i dette tilfellet. Sekvensiell skanning er ikke alltid dårlig, det er tilfeller der sekvensiell skanning er ideell.
4. Nå er det på tide å sjekke spørringen som allerede er gjort på tabellen som har INDEX UNIQUE. La oss sette indeksen og utføre spørringen.
EATE UNIQUE INDEX index_email on users(email);
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Denne gangen benyttet PostgreSQL seg av indeksskanning fordi alle kolonnene som trengs allerede er i indeksen.
Hvis du bare velger noen få linjer, vil det være svært effektivt å bruke indeksen. Hvis du derimot velger flere data, vil det være for tidkrevende å skanne indeksen og tabellen.
Sammendrag
Som du kan se, er kjøretiden for en spørring på en kolonne med en indeks mye kortere (i eksemplet som vises er det en reduksjon fra 1,267 ms til 0,111 ms, så mye som 91,24%!) Den viktigste forskjellen er måten PostgreSQL søker etter posten som interesserer oss. I det første tilfellet måtte databasemotoren søke i hele tabellen etter posten vi trengte. I det andre tilfellet er imidlertid indeksstrukturen sortert og unik, og motoren visste derfor hvor posten befant seg, noe som betydelig fremskyndet tiden for spørringsbehandlingen.
Når det gjelder store databaser og svært komplekse spørringer, kan riktig innstilte indekser gjøre arbeidet med applikasjonen din betydelig raskere uten at du trenger å øke hastigheten på maskinen du søker i databasen på.
Det er verdt å huske at det ikke er en god praksis å opprette indekser på hver kolonne. Etablerte indekser vil gjøre det raskere for optimaliseringsverktøyet å søke etter interessante data, men samtidig gjøre det tregere å sette inn nye og oppdatere eksisterende indekser.