Gør dit Laravel-projekt hurtigere med simpel database indeksering

Databaseindeksering er meget effektiv. Her viser vi hands on, hvordan man bruger simpel indeksering i Laravel, til at gøre ens projekt lynende hurtigt.

Af Jens Just Iversen

10. JAN 2018

Det er nemt at komme i gang med et Laravel-projekt, og oftest vil det kunne klares af et mindre team af programmører. Derfor ender det også i mange tilfælde med, at det er en programmør, der står for databasestrukturen.

Selvom der findes fagfolk, der beskæftiger sig udelukkende med databaser, så følger oprettelse og vedligeholdelse af databasen til et Laravel-projekt oftest naturligt med, som en forlængelse af programmørens arbejde. De mange medfølgende databasedrivere til Laravel integrerer databasemiljøet godt i kodebasen, og de er nemme at benytte. Fx kan der hurtigt oprettes nye tabeller og felter med Laravels schema builder.

Dog ser man nogle gange, at programmører kun kan lave databasearbejde til “husbehov”. Programmøren kan opsætte og vedligeholde basale tabelstrukturer, men er ikke altid helt hjemme i arbejdet med databaser. Vi ser ofte, at overvejelser omkring indeksering af kolonner nedprioriteres.

I denne artikel fortæller jeg, hvordan du nemt og hurtigt identificerer og aktiverer manglende indeksering i dine databasetabeller og dermed øger hastigheden på dit site markant.

Artiklen tager udgangspunkt i MySQL-databaser, som er den mest benyttede database til Laravel.

Hvad er indeksering?

Laver man et opslag i en kolonne uden indeks i en MySQL tabel, bliver MySQL-serveren nødt til at løbe alle rækkerne i tabellen igennem for at se, om der er en række der passer på forespørgslen. Har man derimod oprettet et indeks på kolonnen, så kan MySQL-serveren skyde genvej ved at kigge* i det sorterede indekserede data. Findes den søgte værdi i indekset, returneres den eksakte fysiske position i det rigtige datasæt.

I små datasæt betyder det ikke noget, men i store datasæt kan der være en betydelig hastighedsforskel på, om man bruger indeksering eller ej.

Det vil f.eks. være vigtigt, at der er korrekt indeksering på en tabel med produkter på en webshop. Skal kunderne søge efter produktnavne, kan det tage mange sekunder før siden er færdig indlæst, hvis kolonnen med produktnavne ikke er indekseret. Hvis kolonnen er indekseret, tager det kun et øjeblik.

Eloquent-chainen kunne se sådan ud:

Products::where('name', 'stol')->get();

Og her vil det altså være vigtigt at have oprettet et indeks for kolonnen “name”.

Så lad os indeksere alt!

Det er desværre ikke så enkelt.

Det bliver hurtigt at læse fra en tabel med indeksering, men det tager desværre ekstra tid at skrive til en tabel med indeksering, da indekset skal genopbygges ved hver skrivning. Lav derfor en afvejning ved oprettelsen af et indeks; er det vigtigt at skrivning eller læsning skal gå hurtigt i denne tabel?

En tabel med produkter er oplagt at benytte indeksering på, da det er vigtigt, at kunderne får hurtig respons på front-enden. Derimod kan webshop-administratorerne godt vente lidt, når de gemmer et produkt.

En tabel med logning af besøgenes adfærd på sitet vil til gengæld ikke være god at have indeksering på, da der ofte skal skrives til denne tabel. Udtræk fra en logningstabel vil derimod være i form af rapporter, som kun sjældent skal læses.

Hands on

Har du allerede mistanke til nogle tabeller, der har mangelfuld indeksering, kan du straks udvælge dig disse.

Ellers sæt dig ned og tænk et øjeblik over, hvilke data der ofte læses og skrives fra på din hjemmeside, hvordan fordelingen af skrivning og læsning er, og om det er vigtigt at læsning eller skrivning skal gå hurtigt.

  • Har du mange indlæg, der vises som en blog?
  • Har du mange brugere, der skal kunne søges og sorteres i?
  • Har du mange produkter, som hele tiden hentes information omkring?

Find den valgte tabel i enten dine Laravel database migrations (findes i database/migrations-mappen), eller åben databasen i din MySQL-klient.

Der findes flere forskellige indekseringer. De mest benyttede er “primary key”, “unique” og “index”. Lad os koncentrere os om sidstnævnte, da det vil være her, de fleste lavthængende frugter findes.

Har du behov for at oprette et indeks på en kolonne med produktnavne, gøres det på følgende måde.

$table->index('name');

Migration oprettes med

php artisan make:migration add_index_to_products_table --table=products

Tilføj indekset til din migration. Det kunne f.eks. se sådan ud:

<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddIndexToProductsTable extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->index('name');
        });
    }
    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropIndex('products_name_index');
        });
    }
}

Kør

php artisan migrate

.. og din “products” tabel vil nu have indeksering på kolonnen “name”.

Det er også muligt at tilføje en multi-indeks på følgende måde:

$table->index(['name', 'model']);

Et multi-indeks vil indeksere opslag til alle kolonner i nævnte rækkefølge. Derudover vil opslag til kun første kolonne også være indekserede, men ikke til anden kolonne.

Følgende vil gøre brug af ovenstående indeks:

Products::where('name', 'stol')->get();
Products::where('name', 'stol')->where('model', 'lænestol')->get();

Følgende vil ikke gøre brug af ovenstående indeks:

Products::where('model', 'lænestol')->get();

.. da der ikke er et indeks, der starter på denne måde.

Gør din side hurtigere

Er du ikke vant til at arbejde med databasestruktur, kan det være nyt og lidt skræmmende at arbejde med. Men det er faktisk simpelt. Når du først har prøvet det en enkelt gang afmystificeres det og bliver en helt naturlig del af den løbende optimering. Og det er netop i den løbende optimering, man tit vil få behov for at justere sine indekseringer. Når et projekt er vokset, og der er havnet meget data i databasen, bliver det klart, hvilke tabeller der trænger til et eftersyn.

Lad ikke dine brugere vente, så tag arbejdshandskerne på og kom i sving :-)

Hvis du har spørgsmål eller kommentarer, hører jeg gerne fra dig i kommentarfeltet herunder eller direkte på mail eller telefon.

Tak for at du læste med!

* Hvordan der søges i indekset afhænger af indekstypen. For B-tree indekser benyttes en fremgangsmåde, som forsimplet kan forklares sådan her: Gå til midten af den sorterede data, kommer den søgte værdi før eller efter i alfabetet? Hvis den kommer efter, gå til midten af 2. halvdel af det indekserede data, hvis den kommer før, gå til midten af 1. halvdel af det indekserede data. Kommer den søgte værdi før eller efter i alfabetet på den nye position? Halver igen og gå til midten osv. På denne måde kan en database på ganske få iterationer finde en værdi i et indeks, fordi dataene er sorterede.