Når MySQL-forespørgselsoptimering nævnes, er indekser en af de første ting, der bliver dækket. I dag vil vi prøve at se, hvorfor de er så vigtige.
Hvad er indekser?
Generelt er et indeks en alfabetisk liste over poster med referencer til de sider, hvor de er nævnt. I MySQL er et indeks en datastruktur, der bruges til hurtigt at finde rækker. Indekser kaldes også nøgler, og disse nøgler er afgørende for god ydeevne - efterhånden som dataene vokser sig større, kan behovet for at bruge indekser korrekt blive mere og mere vigtigt. Brug af indekser er en af de mest effektive måder at forbedre forespørgselsydeevnen - hvis indekser bruges korrekt, kan forespørgselsydeevnen stige med tiere eller endda hundredvis af gange.
I dag vil vi forsøge at forklare de grundlæggende fordele og ulemper ved at bruge indekser i MySQL. Husk, at MySQL-indekser alene fortjener en hel bog, så dette indlæg vil ikke dække absolut alt, men det vil være et godt udgangspunkt. For dem, der er interesseret i, hvordan indekser fungerer på et dybere plan, bør læsning af bogen Relational Database Index Design and the Optimizers af Tapio Lahdenmäki og Michael Leach give mere indsigt.
Fordelene ved at bruge indekser
Der er et par hovedfordele ved at bruge indekser i MySQL, og disse er som følger:
- Indekser gør det muligt hurtigt at finde rækker, der matcher en WHERE-sætning;
- Indekser kan hjælpe forespørgsler med at undgå at søge gennem bestemte rækker og dermed reducere mængden af data, som serveren skal undersøge - hvis der er et valg mellem flere indekser, bruger MySQL normalt det mest selektive indeks, dvs. indeks, der finder det mindste antal rækker;
- Indekser kan bruges til at hente rækker fra andre tabeller i JOIN-operationer;
- Indekser kan bruges til at finde minimums- eller maksimumværdien af en specifik kolonne, der bruger et indeks;
- Indekser kan bruges til at sortere eller gruppere en tabel, hvis handlingerne udføres på et præfiks længst til venstre i et indeks - på samme måde kan et præfiks længst til venstre for et indeks med flere kolonner også blive brugt af forespørgselsoptimeringsværktøjet at slå rækker op;
- Indekser kan også bruges til at gemme disk I/O - når et dækkende indeks er i brug, kan en forespørgsel returnere værdier direkte fra indeksstrukturen og gemme disk I/O.
På samme måde er der flere typer indekser:
- INDEX er en type indeks, hvor værdier ikke behøver at være unikke. Denne type indeks accepterer NULL-værdier;
- UNIQUE INDEX bruges ofte til at fjerne duplikerede rækker fra en tabel - denne type indeks giver udviklere mulighed for at håndhæve rækkeværdiernes unikke karakter;
- FULLTEXT INDEX er et indeks, der anvendes på felter, der bruger fuldtekstsøgningsfunktioner. Denne type indeks finder nøgleord i teksten i stedet for direkte at sammenligne værdier med værdierne i indekset;
- DESCENDING INDEX er et indeks, der gemmer rækker i faldende rækkefølge - forespørgselsoptimeringsværktøjet vil vælge denne type indeks, når forespørgslen anmoder om en faldende rækkefølge. Denne indekstype blev introduceret i MySQL 8.0;
- PRIMÆR NØGLE er også et indeks. I en nøddeskal er den PRIMÆRE NØGLE en kolonne eller et sæt kolonner, der identificerer hver række i en tabel - ofte brugt sammen med felter med en AUTO_INCREMENT-attribut. Denne type indeks accepterer ikke NULL-værdier, og når de først er indstillet, kan værdierne i PRIMÆR NØGLE ikke ændres.
Nu vil vi prøve at gennemgå både fordele og ulemper ved at bruge indekser i MySQL. Vi starter med den sandsynligvis oftest diskuterede opside - fremskyndelse af forespørgsler, der matcher en WHERE-klausul.
Fremskyndelse af forespørgsler, der matcher en WHERE-klausul
Indekser bruges ofte til at fremskynde søgeforespørgsler, der matcher en WHERE-sætning. Grunden til, at et indeks gør sådanne søgeoperationer hurtigere, er ret simpel - forespørgsler, der bruger et indeks, undgår en fuld tabelscanning.
For at fremskynde forespørgsler, der matcher en WHERE-sætning, kan du bruge EXPLAIN-sætningen i MySQL. Udsagnet EXPLAIN SELECT skulle give dig lidt indsigt i, hvordan MySQL-forespørgselsoptimeringsværktøjet udfører forespørgslen - den kan også vise dig, om den pågældende forespørgsel bruger et indeks eller ej, og hvilket indeks den bruger. Tag et kig på følgende forespørgselsforklaring:
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: NULL
key: NULL
key_len: NULL
<...>
Ovenstående forespørgsel bruger ikke et indeks. Men hvis vi tilføjer et indeks på "field_1", ville indekset blive brugt med succes:
mysql> EXPLAIN SELECT * FROM demo_table WHERE field_1 = “Demo” \G;
*************************** 1. row ***************************
<...>
possible_keys: field_1
key: field_1
key_len: 43
<...>
Søjlen possible_keys beskriver de mulige indekser, som MySQL kan vælge, nøglekolonnen beskriver det faktisk valgte indeks, og kolonnen key_len beskriver længden af den valgte nøgle.
I dette tilfælde vil MySQL udføre et opslag af værdierne i indekset og returnere alle rækker, der indeholder den angivne værdi - som et resultat, ville forespørgslen være hurtigere. Selvom indekser hjælper visse forespørgsler med at være hurtigere, er der et par ting, du skal huske på, hvis du vil have, at dine indekser skal hjælpe dine forespørgsler:
- Isoler dine kolonner - MySQL kan ikke bruge indekser, hvis kolonnerne indekserne bruges på ikke er isolerede. For eksempel ville en forespørgsel som denne ikke bruge et indeks:
SELECT field_1 FROM demo_table WHERE field_1 + 5 = 10;
For at løse dette, lad kolonnen, der går efter WHERE-sætningen alene - forenkle din forespørgsel så meget som muligt og isoler kolonnerne;
- Undgå at bruge LIKE-forespørgsler med et forudgående jokertegn - i dette tilfælde vil MySQL ikke bruge et indeks, fordi det foregående jokertegn betyder, at der kan være hvad som helst før teksten. Hvis du skal bruge LIKE-forespørgsler med jokertegn og ønsker, at forespørgslerne skal gøre brug af indekser, skal du sørge for, at jokertegnet er i slutningen af søgesætningen.
At fremskynde forespørgsler, der matcher en WHERE-klausul, kan selvfølgelig også gøres på andre måder (for eksempel partitionering), men for nemheds skyld vil vi ikke se nærmere på det i dette indlæg.
Det, vi dog kunne være interesseret i, er forskellige slags indekstyper, så det vil vi se nærmere på nu.
Slik af med duplikerede værdier i en kolonne - UNIKKE indekser
Formålet med et UNIKT indeks i MySQL er at håndhæve det unikke af værdierne i en kolonne. For at bruge et UNIKT indeks skal du køre en CREATE UNIQUE INDEX-forespørgsel:
CREATE UNIQUE INDEX demo_index ON demo_table(demo_column);
You can also create a unique index when you create a table:
CREATE TABLE demo_table (
`demo_column` VARCHAR(100) NOT NULL,
UNIQUE KEY(demo_column)
);
Det er alt, der skal til for at tilføje et unikt indeks til en tabel. Når du nu prøver at tilføje en dubletværdi til tabellen, vil MySQL komme tilbage med følgende fejl:
#1062 - Duplicate entry ‘Demo’ for key ‘demo_column’
FULLTEXT-indekser
Et FULLTEXT-indeks er et sådant indeks, der anvendes til de kolonner, der bruger fuldtekstsøgningsfunktioner. Denne type indeks har mange unikke muligheder, herunder stopord og søgetilstande.
InnoDB-stopordslisten har 36 ord, mens MyISAM-stopordslisten har 143. I InnoDB er stopordene afledt fra tabellen sat i variablen innodb_ft_user_stopword_table, ellers, hvis denne variabel ikke er indstillet, er de afledt fra variabelen innodb_ft_server_stopword_table. Hvis ingen af disse to variable er indstillet, bruger InnoDB den indbyggede liste. For at se standard-InnoDB-stopordslisten skal du forespørge på INNODB_FT_DEFAULT_STOPWORD-tabellen.
I MyISAM er stopordene afledt fra filen storage/myisam/ft_static.c. Variablen ft_stopword_file gør det muligt at ændre standard stopordslisten. Stopord vil blive deaktiveret, hvis denne variabel er sat til en tom streng, men husk, at hvis denne variabel definerer en fil, bliver den definerede fil ikke parset for kommentarer - MyISAM vil behandle alle de ord, der findes i filen, som stopord.
FULLTEXT-indekserne er også berømte for sine unikke søgetilstande:
- Hvis en FULLTEXT-søgeforespørgsel uden modifikatorer køres, aktiveres en naturlig sprogtilstand. Den naturlige sprogtilstand kan også aktiveres ved at bruge modifikatoren I NATURAL LANGUAGE MODE;
- WITH QUERY EXPANSION-modifikatoren aktiverer en søgetilstand med forespørgselsudvidelse. En sådan søgetilstand fungerer ved at udføre søgningen to gange, og når søgningen køres for anden gang, vil resultatsættet indeholde et par af de mest relevante dokumenter fra den første søgning. Generelt er denne modifikator nyttig, når brugeren har en vis underforstået viden (for eksempel kan brugeren søge efter "database" og håbe på at se "InnoDB" og "MyISAM" i resultatsættet);
- Modifier I BOOLEAN MODE tillader søgning med booleske operatorer. For eksempel ville +, - eller * operatorerne hver udføre forskellige opgaver - + operatoren ville definere at værdien skal være til stede i en række, - operatoren ville definere at værdien ikke må eksistere og * operatoren ville fungere som en jokertegn.
En forespørgsel, der bruger et FULLTEXT-indeks, ser sådan ud:
SELECT * FROM demo_table WHERE MATCH(demo_field) AGAINST(‘value’ IN NATURAL LANGUAGE MODE);
Husk på, at FULLTEXT-indekser generelt er nyttige til MATCH() AGAINST()-operationer - ikke til WHERE-operationer, hvilket betyder, at hvis en WHERE-sætning ville blive brugt, nytten af at bruge forskellige indekstyper ville ikke blive elimineret.
Det er også værd at nævne, at FULLTEXT-indekser har en minimumslængde af tegn. I InnoDB kan en FULLTEXT-søgning kun udføres, når søgeforespørgslen består af minimum tre tegn - denne grænse er øget til fire tegn i MyISAM-lagringsmaskinen.
FALDENDE indekser
Et FALDENDE indeks er et sådant indeks, hvor InnoDB gemmer indtastningerne i en faldende rækkefølge - forespørgselsoptimeringsværktøjet vil bruge et sådant indeks, når en faldende rækkefølge anmodes af forespørgslen. Et sådant indeks kan tilføjes til en kolonne ved at køre en forespørgsel som nedenfor:
CREATE INDEX descending_index ON demo_table(column_name DESC);
Et stigende indeks kan også tilføjes til en kolonne - bare udskift DESC med ASC.
PRIMÆRE NØGLER
En PRIMÆR NØGLE fungerer som en unik identifikator for hver række i en tabel. En kolonne med en PRIMÆR NØGLE skal indeholde unikke værdier - der må heller ikke bruges NULL-værdier. Hvis en dubletværdi tilføjes til en kolonne, som har en PRIMÆR NØGLE, vil MySQL svare med en fejl #1062:
#1062 - Duplicate entry ‘Demo’ for key ‘PRIMARY’
Hvis der tilføjes en NULL-værdi til kolonnen, vil MySQL svare med en fejl #1048:
#1048 - Column ‘id’ cannot be null
Primære indekser kaldes også nogle gange klyngede indekser (vi diskuterer dem senere).
Du kan også oprette indekser på flere kolonner på én gang - sådanne indekser kaldes multikolonneindekser.
Flerkolonneindekser
Indekser på flere kolonner bliver ofte misforstået - nogle gange indekserer udviklere og DBA'er alle kolonnerne separat eller indekserer dem i den forkerte rækkefølge. For at gøre forespørgsler, der anvender indekser med flere kolonner, så effektive som muligt, skal du huske, at rækkefølgen af kolonner i indekser, der bruger mere end én kolonne, er en af de mest almindelige årsager til forvirring i dette rum - da der ikke er "denne vej eller motorvejen" ” indeksrækkefølgeløsninger, skal du huske, at den korrekte rækkefølge af multikolonneindekser afhænger af de forespørgsler, der bruger indekset. Selvom dette kan virke ret indlysende, skal du huske, at kolonnerækkefølgen er afgørende, når du har at gøre med indekser med flere kolonner - vælg kolonnerækkefølgen, så den er så selektiv som muligt for de forespørgsler, der kører oftest.
For at måle selektiviteten for specifikke kolonner, få forholdet mellem antallet af distinkte indekserede værdier og det samlede antal rækker i tabellen - den kolonne, der har den højeste selektivitet, skal være den første .
Nogle gange skal du også indeksere meget lange tegnkolonner, og i så fald kan du ofte spare tid og ressourcer ved at indeksere de første par tegn - et præfiks - i stedet for hele værdien.
Præfiksindekser
Præfiksindekser kan være nyttige, når kolonnerne indeholder meget lange strengværdier, hvilket ville betyde, at tilføjelse af et indeks på hele kolonnen ville forbruge meget diskplads. MySQL hjælper med at løse dette problem ved at tillade dig kun at indeksere et præfiks af værdien, hvilket igen gør indeksstørrelsen mindre. Tag et kig:
CREATE TABLE `demo_table` (
`demo_column` VARCHAR(100) NOT NULL,
INDEX(demo_column(10))
);
Ovenstående forespørgsel ville skabe et præfiksindeks på demokolonnen, der kun indekserer de første 10 tegn i værdien. Du kan også tilføje et præfiksindeks til en eksisterende tabel:
CREATE INDEX index_name ON table_name(column_name(length));
Så, hvis du for eksempel ønsker at indeksere de første 5 tegn i en demo_column på en demo_table, kan du køre følgende forespørgsel:
CREATE INDEX demo_index ON demo_table(demo_column(5));
Du bør vælge et præfiks, der er langt nok til at give selektivitet, men også kort nok til at give plads. Dette kan dog være lettere sagt end gjort - du skal eksperimentere og finde den løsning, der virker for dig.
Dækkende indekser
Et dækkende indeks "dækker" alle de nødvendige felter for at udføre en forespørgsel. Med andre ord, når alle felter i en forespørgsel er dækket af et indeks, er et dækkende indeks i brug. For eksempel for en forespørgsel som sådan:
SELECT id, title FROM demo_table WHERE id = 1;
Et dækkende indeks kan se sådan ud:
INDEX index_name(id, title);
Hvis du vil sikre dig, at en forespørgsel bruger et dækkende indeks, skal du udsende en EXPLAIN-sætning på den, og derefter tage et kig på kolonnen Ekstra. For eksempel, hvis din tabel har et indeks med flere kolonner på id og titel, og en forespørgsel, der kun har adgang til disse to kolonner, udføres, vil MySQL bruge indekset:
mysql> EXPLAIN SELECT id, title FROM demo_table \G;
*************************** 1. row ***************************
<...>
type: index
key: index_name
key_len: 5
rows: 1000
Extra: Using index
<...>
Husk på, at et dækkende indeks skal gemme værdierne fra de kolonner, det dækker. Det betyder, at MySQL kun kan bruge B-Tree-indekser til at dække forespørgsler, fordi andre slags indekser ikke gemmer disse værdier.
Klyngede, sekundære indekser og indekskardinalitet
Når indekser diskuteres, hører du måske også udtrykkene klyngede, sekundære indekser og indekskardinalitet. Kort sagt er klyngede indekser en tilgang til datalagring, og alle andre indekser end klyngede indekser er sekundære indekser. Indekskardinalitet på den anden side er antallet af unikke værdier i et indeks.
Et klynget indeks fremskynder forespørgsler, fordi tætte værdier også er gemt tæt på hinanden på disken, men det er også grunden til, at du kun kan have ét klynget indeks i en tabel.
Et sekundært indeks er ethvert indeks, der ikke er det primære indeks. Et sådant indeks kan have dubletter.
Ulemperne ved at bruge indekser
Brugen af indekser har bestemt fordele, men vi må ikke glemme, at indekser også kan være en af de førende årsager til problemer i MySQL. Nogle af ulemperne ved at bruge indekser er som følger:
- Indekser kan forringe ydeevnen af visse forespørgsler - selvom indekser har en tendens til at fremskynde ydeevnen af SELECT-forespørgsler, sænker de ydeevnen af INSERT-, UPDATE- og DELETE-forespørgsler, fordi når dataene opdateres indekset skal også opdateres sammen med det:enhver handling, der involverer manipulation af indekserne, vil være langsommere end normalt;
- Indekser bruger diskplads - et indeks optager sin egen plads, så indekserede data vil også bruge mere diskplads;
- Redundante og duplikerede indekser kan være et problem - MySQL giver dig mulighed for at oprette duplikerede indekser på en kolonne, og det "beskytter dig" ikke mod at gøre sådan en fejl. Tag et kig på dette eksempel:
CREATE TABLE `demo_table` ( `id` INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, `column_2` VARCHAR(10) NOT NULL, `column_3` VARCHAR(10) NOT NULL, INDEX(id), UNIQUE(id) );
En uerfaren bruger tror måske, at denne forespørgsel får id-kolonnen til at øges automatisk, og tilføjer derefter et indeks på kolonnen og får kolonnen til ikke at acceptere duplikerede værdier. Det er dog ikke det, der sker her. I dette tilfælde har den samme kolonne tre indekser på sig:et almindeligt INDEX, og da MySQL implementerer både PRIMÆR NØGLE og UNIKKE begrænsninger med indekser, tilføjer det yderligere to indekser på den samme kolonne!
Konklusion
For at konkludere, har indekser i MySQL deres egen plads - indekser kan bruges i et væld af scenarier, men hver af disse brugsscenarier har deres egne ulemper, som skal overvejes for at få mest muligt ud af indekser, der er i brug.
For at bruge indekser godt, profilere dine forespørgsler, tage et kig på hvilke muligheder du har, når det kommer til indekser, kende deres fordele og ulemper, beslutte hvilke indekser du har brug for baseret på dine krav, og efter du har indekseret kolonnerne, sørg for at dine indekser er faktisk brugt af MySQL. Hvis du har indekseret dit skema korrekt, bør ydeevnen af dine forespørgsler forbedres, men hvis responstiden ikke tilfredsstiller dig, så se om der kan oprettes et bedre indeks for at forbedre det.