Představte si běžné pondělní ráno v internetovém obchodě. Zákazník si vybere boty, otevře detail produktu a čeká. Ne dlouho, jen dvě nebo tři sekundy. Pak ještě trochu. Potom stránku zavře a jde jinam, protože trpělivost není mezi povinnou výbavou online nakupujícího. Mezitím server pilně prohledává databázi, aby našel několik údajů, které by správně měl získat téměř okamžitě.
Právě tady se ukazuje, proč databáze není jen nenápadná skříň v pozadí webu. Když jsou dotazy špatně napsané, indexy chybí nebo databázový engine pracuje se zastaralým odhadem, zpomalí se načítání stránky, administrace i API. Uživatel vidí pomalý web. Vývojář vidí SQL dotaz. Server mezitím vidí další pracovní den, který právě začal velmi neochotně.
Rozdíl mezi dobře a špatně optimalizovaným dotazem může být překvapivě velký. V jednom testu MySQL vracel dotaz pouhých 18 řádků, ale bez vhodného indexu musel prohledat přibližně 1,5 milionu řádků. Po změně na indexovaný range scan klesl počet prohledaných řádků zhruba na 33 tisíc. Výsledek se nezměnil, změnila se jen cesta k němu. Je to podobné, jako kdybyste v knihovně nehledali konkrétní knihu podle katalogu, ale postupně kontrolovali každý svazek, včetně kuchařek a telefonních seznamů.
Databázový index funguje jako uspořádaná pomůcka pro hledání. U tabulky s 500 000 řádky odhaduje dokumentace MySQL přístup přes B-tree přibližně na čtyři disková vyhledání. Příslušný index přitom může zabrat asi 5,2 MB. To není žádná tragédie v době, kdy i levnější telefon nosí v kapse úložiště měřené ve stovkách gigabajtů. Index ale není kouzelná nálepka, kterou stačí nalepit na každý sloupec.
Každý index něco stojí. Při vložení, změně nebo smazání záznamu se musí aktualizovat všechny dotčené indexy. Čtení tedy zrychlí, zápis může zpomalit. Web, který hlavně zobrazuje katalog, bude potřebovat jiné kompromisy než systém, do něhož každou sekundu proudí objednávky, skladové pohyby a záznamy o aktivitě uživatelů. Přidat index na každý sloupec je databázová obdoba toho, když si člověk na pracovní stůl postaví cedulku pro každou tužku. Orientace bude skvělá, práce o něco méně.
Problém často nezačíná u samotné databáze, ale u dotazu. Aplikace může opakovaně načítat stejné údaje, spojovat tabulky bez vhodných podmínek nebo si vyžádat všechny sloupce, přestože potřebuje jen dva. Dotaz pak vypadá nevinně, zvlášť když nad malým testovacím datasetem odpoví rychle. Po měsících provozu a několika stech tisících záznamů se z něj může stát nenápadný žrout výkonu.
Zvlášť zrádný bývá takzvaný full-table scan, tedy průchod celou tabulkou. Databáze při něm kontroluje řádky jeden po druhém, aby zjistila, které vyhovují podmínce. U malé tabulky to nemusí vadit. U velké tabulky se z milisekund snadno stanou sekundy a z jednoho dotazu série dalších dotazů, které čekají ve frontě. Jestli stejný mechanismus obsluhuje stovky požadavků současně, problém už není lokální. Začne se šířit celým webem jako pomluva v malé kanceláři.
Dopad se projeví i v metrikách, které uživatel přímo nevidí. Google považuje za dobrý výsledek největší vykreslený prvek stránky, tedy LCP, do 2,5 sekundy. U interakce s webem, kterou měří INP, je hranice dobrého výsledku 200 milisekund a kumulativní posun obsahu CLS by měl zůstat do 0,1. Databáze do těchto hodnot nemusí vstupovat sama, ale pomalá odpověď serveru se do celkového dojmu propíše velmi rychle. TTFB nad 1,8 sekundy už Google hodnotí jako špatný. Pokud server čeká na databázi, prohlížeč mezitím nemá co zobrazovat, i kdyby byl frontend napsaný ukázkově.
Častou chybou je sledovat jen průměrnou rychlost. Průměr může vypadat hezky, zatímco část požadavků se vleče desítky sekund. Uživatel, kterému se stránka načítá pomalu právě dnes, z průměru žádnou útěchu nemá. Proto dává smysl sledovat pomalé dotazy, jejich četnost a kontext, v němž vznikají. MySQL má pro tento účel slow query log. Jeho výchozí hodnota `long_query_time` je 10 sekund, což je pro diagnostiku základní síť, nikoli ideální hranice pro každý web. Záznam může zachytit také dotazy, které nepoužívají index.
První rozumný krok bývá prostý: najít dotaz, který skutečně brzdí provoz, a prohlédnout si jeho execution plan. Ten ukáže, zda databáze použila index, kolik řádků odhaduje a jakým způsobem tabulky spojuje. Bez této kontroly se optimalizace snadno změní v hádání. Někdo přidá index, někdo přepíše dotaz a někdo třetí slavnostně zvýší výkon serveru. Databáze pak možná dostane více paměti, ale stále hledá špatným směrem.
Své dokáže udělat i aktualizace statistik tabulek. MySQL doporučuje použít `ANALYZE TABLE`, protože zastaralé statistiky mohou vést k neefektivnímu execution plánu. Databáze se při rozhodování opírá o odhady velikosti tabulek a rozložení hodnot. Když jsou tyto informace mimo realitu, může zvolit cestu, která na papíře vypadá levně, ale v provozu připomíná výpravu pro jeden rohlík přes tři okresy.
Optimalizace přitom neznamená slepě zkracovat každý dotaz. Někdy je lepší omezit množství načítaných dat, jindy změnit pořadí operací, upravit podmínku nebo rozdělit příliš široký dotaz. U často používaných údajů může pomoci mezipaměť, ale ta jen odkládá problém, pokud se pod ní dál ukrývá neefektivní SQL. A při práci s indexy je třeba myslet na zápisy, velikost databáze i skutečné použití aplikace.
Nejhorší databázové potíže obvykle nevzniknou jedním dramatickým rozhodnutím. Přibude několik funkcí, pár nových filtrů, větší tabulka a další report, který měl být původně jen „na rychlé ověření“. Každý nový kus vypadá neškodně. Dohromady ale mohou způsobit, že web čeká na databázi déle než zákazník ochotný znovu načíst stránku.















