Preskočiť na obsah
Databázy PHP 4. august 2026 · 13 min čítania

Pomalé MySQL query: ako nájsť úzke hrdlo

Aplikácia, ktorá beží roky, sa zo dňa na deň spomalí. Server je pritom nevyťažený a kód sa nemenil. V drvivej väčšine prípadov je príčina rovnaká: tabuľka prerástla veľkosť, pri ktorej si MySQL dovolil sekvenčné prehľadávanie, a chýba jeden index. Tento článok ukazuje, ako takú query nájsť a opraviť — bez hádania.

DC

Dušan Chlpek

PHP vývojár, GEAR s.r.o. · 25+ rokov praxe

Najprv merajte, potom optimalizujte

Najčastejšia chyba pri riešení pomalej databázy je začať pridávaním indexov na všetko, čo vyzerá podozrivo. Výsledkom je databáza s dvadsiatimi indexmi, pomalým zápisom a rovnako pomalým čítaním. Optimalizácia bez merania je hádanie.

Poradie krokov, ktoré funguje:

  1. Zapnúť slow query log a nechať ho bežať aspoň deň pri reálnej prevádzke.
  2. Zoradiť zachytené query podľa celkového času, nie podľa najhoršieho jedného behu.
  3. Najhoršiu query rozobrať cez EXPLAIN.
  4. Nasadiť jednu zmenu a zmerať dopad.
  5. Opakovať, kým sa to oplatí.

Zapnutie slow query logu

-- bez reštartu servera
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;          -- sekundy
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- kontrola nastavenia
SHOW VARIABLES LIKE 'slow_query%';

Hranicu long_query_time nastavte nízko — pol sekundy je rozumný začiatok. Query, ktorá trvá 200 ms, ale vykoná sa tisíckrát za minútu, zaťažuje server viac než tá, čo trvá päť sekúnd raz za hodinu.

Voľbu log_queries_not_using_indexes nenechávajte zapnutú dlhodobo. Na vyťaženom serveri vygeneruje gigabajty logov za pár hodín. Zapnite ju na analýzu a potom vypnite.

Vyhodnotenie logu

Surový log sa nečíta dobre. Nástroj mysqldumpslow je súčasťou inštalácie MySQL a výsledky zoskupí podľa tvaru query:

# desať query s najvyšším súčtom času
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# zoradenie podľa počtu volaní
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log

Zoskupenie je podstatné: rovnaká query s tisíckou rôznych parametrov sa zobrazí ako jeden riadok so súčtom. Práve ten súčet hovorí, kde je skutočný problém.

EXPLAIN: čo MySQL naozaj robí

EXPLAIN ukáže plán vykonania. Postavený pred SELECT vypíše, ktoré indexy sa použijú a koľko riadkov MySQL očakáva, že bude musieť prejsť:

EXPLAIN SELECT o.id, o.suma, z.email
FROM objednavky o
JOIN zakaznici z ON z.id = o.zakaznik_id
WHERE o.stav = 'nova'
  AND o.vytvorene_at >= '2026-07-01'
ORDER BY o.vytvorene_at DESC
LIMIT 50;

Vo výstupe sledujte tri stĺpce:

Pre skutočné čísla namiesto odhadov použite EXPLAIN ANALYZE, ktorý query naozaj spustí a vypíše namerané časy jednotlivých krokov.

Indexy: ako ich navrhnúť správne

Index je usporiadaná kópia vybraných stĺpcov s odkazom na riadok. Vďaka tomu MySQL nájde záznam bez prechádzania celej tabuľky. Základné pravidlá:

Indexujte to, podľa čoho filtrujete a triedite

Kandidáti sú stĺpce v WHERE, JOIN, ORDER BYGROUP BY. Naopak, indexovať stĺpec, ktorý sa len vypisuje, nemá zmysel.

Poradie stĺpcov v zloženom indexe rozhoduje

Zložený index (stav, vytvorene_at) sa dá použiť pre podmienku na stav samotný aj pre kombináciu oboch stĺpcov. Pre podmienku len na vytvorene_at sa použiť nedá. Platí pravidlo najľavejšieho prefixu:

CREATE INDEX idx_objednavky_stav_datum
    ON objednavky (stav, vytvorene_at);

-- index sa použije
SELECT * FROM objednavky WHERE stav = 'nova';
SELECT * FROM objednavky WHERE stav = 'nova' AND vytvorene_at >= '2026-07-01';

-- index sa nepoužije
SELECT * FROM objednavky WHERE vytvorene_at >= '2026-07-01';

Praktický dôsledok: namiesto troch samostatných indexov často stačí jeden dobre poskladaný zložený. Do popredia dávajte stĺpce s presnou zhodou (=), rozsahové podmienky nakoniec.

Pokrývajúci index ako zrýchlenie zadarmo

Ak index obsahuje všetky stĺpce, ktoré query potrebuje, MySQL vôbec nemusí siahať do tabuľky. V EXPLAIN to uvidíte ako Using index:

CREATE INDEX idx_objednavky_prehlad
    ON objednavky (stav, vytvorene_at, suma);

SELECT stav, vytvorene_at, suma
FROM objednavky
WHERE stav = 'nova'
ORDER BY vytvorene_at DESC
LIMIT 100;

Kedy index nepomôže

Každý index spomaľuje zápis. Pri INSERTUPDATE musí MySQL aktualizovať všetky indexy tabuľky. Na tabuľke s vysokou frekvenciou zápisu je desať indexov citeľná brzda. Nepoužívané indexy zmažte.

N+1 problém: najčastejšia príčina v Laraveli

Druhá polovica pomalých aplikácií nemá problém s jednou ťažkou query, ale s tristo rýchlymi. Klasický N+1: načítate zoznam a pre každý záznam sa v šablóne dotiahne súvisiaci objekt samostatnou query.

// zlé — 1 + N query
$objednavky = Objednavka::where('stav', 'nova')->get();
foreach ($objednavky as $objednavka) {
    echo $objednavka->zakaznik->email;   // query pre každý riadok
}

// dobré — 2 query bez ohľadu na počet riadkov
$objednavky = Objednavka::with('zakaznik')
    ->where('stav', 'nova')
    ->get();

Aby sa taká chyba nedostala do produkcie, zapnite si v lokálnom prostredí prísny režim, ktorý na lenivé načítanie vzťahu vyhodí výnimku:

// app/Providers/AppServiceProvider.php
public function boot(): void
{
    Model::preventLazyLoading(! app()->isProduction());
}

Pri väčších zoznamoch nenačítavajte celé modely, keď potrebujete tri stĺpce. Metóda select() a spracovanie po dávkach cez chunkById() ušetria aj pamäť:

Objednavka::select(['id', 'suma', 'zakaznik_id'])
    ->where('stav', 'nova')
    ->chunkById(500, function ($davka) {
        // spracovanie 500 riadkov naraz
    });

Keď indexy nestačia

Ak je query optimalizovaná a stále trvá dlho, problém je inde. Typické situácie a riešenia:

Nastavenie servera, ktoré stojí za kontrolu

Pri InnoDB má najväčší vplyv veľkosť vyrovnávacej pamäte. Ak je menšia než pracovná množina dát, server číta z disku aj to, čo by mal mať v pamäti:

SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

-- veľkosť dát a indexov podľa tabuliek
SELECT table_name,
       ROUND((data_length + index_length) / 1024 / 1024) AS mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC
LIMIT 10;

Na vyhradenom databázovom serveri býva rozumné nastaviť innodb_buffer_pool_size na 60 až 70 % dostupnej pamäte. Na zdieľanom serveri, kde beží aj PHP, postupujte opatrnejšie.

Zhrnutie: postup pri pomalej databáze

Spomaľuje sa vám aplikácia a neviete prečo?

Robím audit výkonu databázy a aplikácie — nájdem konkrétne query, ktoré vás brzdia, a odovzdám zoznam opráv zoradený podľa dopadu. Typicky do dvoch dní od prístupu na server.

Ďalšie články

Zavolať E-mail Dopyt

Ochrana súkromia

Táto stránka využíva cookies pre nevyhnutné fungovanie. Rešpektujeme vaše súkromie a legislatívu GDPR.