Når vi arbejder med Ruby on Rails-frameworket, arbejder vi normalt med relationsdatabaser som MySQL eller PostgreSQL. Når vi definerer migreringer ved hjælp af Active Record Migrations, støder vi på de såkaldte indekser, men begyndere forstår ofte ikke helt indekser, og hvilke fordele de giver.
Når vi arbejder med Ruby on Rails-frameworket, arbejder vi normalt med relationsdatabaser som MySQL eller PostgreSQL. Når vi definerer migreringer ved hjælp af Active Record Migrations, støder vi på de såkaldte indekser, men begyndere forstår ofte ikke helt indekser, og hvilke fordele de giver.
I dette indlæg vil jeg gerne forklare, hvad indekser er, hvad de bruges til, og præsentere nogle gode eksempler på, hvordan man bruger dem.
Database
Der findes mange databasemotorer, og en af de mest populære er de tidligere nævnte MySQL, PostgreSQL, Oracle eller Microsoft SQL Server. De er alle relationsdatabaser, hvilket betyder, at alle data er relateret til hinanden og gemt i tabeller. Hver tabelrække kaldes en post, og hver post har sin egen unikke identifikator (id). Du kan tjekke rangordningen af de mest populære databasemotorer på https://db-engines.com/en/ranking. Du kan også finde nogle ikke-relationelle databaser der, f.eks. MongoDB.
Oprettelse af et indeks
Tabellerne i vores databaser kan have fra nogle få til flere dusin - i ekstreme tilfælde op til flere hundrede - kolonner. Husk, at hver tabel kan have et ubegrænset antal rækker. Dette antal er ikke et direkte resultat af databasens struktur, og vi bør altid antage, at antallet af poster vil stige successivt, og at vores database derfor vil vokse. De første antagelser og forespørgsler, der er skrevet i eksisterende applikationer, kan være gode til et lille eller mellemstort antal poster, men med tiden, når der kommer flere data, ophører applikationens kommunikation med databasen med at være effektiv.
Programmørens rolle er at skrive forespørgsler for at hente nogle data fra tabellen eller tabellerne, men den optimale måde at behandle forespørgslen på afhænger af databasemotoren. Husk, at databasemotorer indlæser data fra disken til hukommelsen og derefter scanner dem. Det betyder, at hvis mange brugere udfører komplekse operationer på samme tid, vil flere af dem være nødt til at vente på deres tur på grund af manglende ressourcer til at gennemføre deres søgninger. Det er derfor, relevante indekser er så vigtige.
Wiki: Indeks - en datastruktur, der gør det hurtigere at foretage søgninger i en tabel.
For hvert indeks skal vi definere nøgler (for en eller flere kolonner), som skal bruges til at søge efter poster i tabellen. Dataene i indekset vil blive sorteret efter den nøgle, der tidligere er blevet defineret, hvilket vil gøre det betydeligt hurtigere at søge efter data i tabellen. Det enkleste eksempel fra hverdagen er en telefonbog, hvor folk er sorteret efter for- og efternavn. Man kan sige, at vores indeks i dette tilfælde vil være for- og efternavn.
Hvordan vælger man den bedste indeksnøgle? Det er ikke svært - bare husk et par regler. Opret et indeks baseret på kolonner, der:
- vil ofte blive brugt i vores forespørgsler (WHERE),
- i kombination med hinanden giver en unik værdi (dvs. en, der angiver præcis én række),
- vil blive brugt som såkaldte forbindelseskolonner (JOIN),
- giver de mest selektive nøgler, dvs. dem, der giver det mindste antal linjer, når man skriver en forespørgsel.
Hvis vi allerede ved, hvilke nøgler der vil være optimale for vores tabel, kan vi også spørge os selv, hvor mange indekser vi har brug for. I dette tilfælde er det bedst at kende de forespørgsler, der vil henvise til vores tabel, allerede i designfasen.
Lad os oprette indekser til specifikke forespørgsler, der vil blive vist, men lad være med at skrive dem for hver kolonne. Indekser skal ligesom tabeller gemmes et sted, så når vi opretter tabeller med et indeks for hver kolonne, skal vi tage højde for, at den plads, der bruges, kan stige betydeligt.
Opret et unikt indeks
Et andet spørgsmål, vi skal tænke over, er unikhed. Det er værd at bruge de ekstra fem minutter på at overveje, om vores indeks virkelig er unikt. På den måde fortæller vi forespørgselsoptimeringen, at den ikke behøver at forvente dubletter i forespørgslen. For eksempel e-mailadresser:
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
I eksemplet med PostgreSQL-motoren vil jeg vise forskellen i forespørgselshastighed på e-mail-kolonnen med et unikt indeks og uden et indeks.
1. Du kan bruge eksempler Kode uddrag på din egen database for at kunne teste eksemplet nedenfor. Lad os først oprette en tom tabel med én kolonne:
CREATE TABLE users (
e-mail varchar
);
2. Lad os generere 10.000 poster til testen:
DO $
BEGIN FOR i IN 1..10000 LOOP
INSERT INTO users values((select 'user' || i || '@example.com'));
END LOOP; END;
$;
Vi vil bruge EXPLAIN ANALYZE til at tjekke, hvor hurtigt vores forespørgsel bliver behandlet, når vi vil finde en bestemt bruger i databasen.
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Vores forespørgsel tvang os til at iterere rundt i hele tabellen på jagt efter den post, der interesserede os.
Denne proces kaldes sekventiel scanning. I dette tilfælde er den bedste måde at udføre opgaven på at læse hele tabellen og filtrere bestemte rækker fra.
PostgreSQL vil filtrere de unødvendige linjer fra og blot returnere dem, der interesserer os. Det er virkelig den bedste ting at gøre i dette tilfælde. Sekventiel scanning er ikke altid dårlig, der er tilfælde, hvor sekventiel scanning er ideel.
4. Nu er det tid til at tjekke den forespørgsel, der allerede er lavet på den tabel, der har INDEX UNIQUE. Lad os indstille indekset og udføre forespørgslen.
EATE UNIQUE INDEX index_email on users(email);
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Denne gang benyttede PostgreSQL sig af indeksscanning, fordi alle de nødvendige kolonner allerede findes i indekset.
Hvis man kun vælger nogle få linjer, er det meget effektivt at bruge indekset. Men hvis der vælges flere data, vil det være for tidskrævende at scanne indekset og tabellen.
Sammenfatning
Som du kan se, er udførelsestiden for en forespørgsel på en kolonne med et indeks meget kortere (i det viste eksempel er det et fald fra 1,267 ms til 0,111 ms, så meget som 91,24%!) Den vigtigste forskel er den måde, PostgreSQL søger efter den post, der interesserer os. I det første tilfælde var databasemotoren nødt til at søge i hele tabellen efter den post, vi havde brug for. I det andet tilfælde er indeksstrukturen imidlertid sorteret og unik, og derfor vidste motoren, hvor posten befandt sig, hvilket fremskyndede forespørgselsbehandlingen betydeligt.
I tilfælde af store databaser og meget komplekse forespørgsler kan korrekt indstillede indekser fremskynde arbejdet i dit program betydeligt, uden at det er nødvendigt at øge hastigheden på den maskine, du søger i databasen på.
Det er værd at huske, at det ikke er en god praksis at oprette indekser på hver enkelt kolonne. Etablerede indekser vil fremskynde optimeringsprogrammets arbejde med at søge efter interessante data, men samtidig gøre det langsommere at indsætte nye og opdatere eksisterende.