Proč indexovat: principy, cíle a kompromisy
Indexování v relačních databázích (PostgreSQL, MySQL/InnoDB, SQL Server, Oracle aj.) je klíčové pro rychlé vyhledávání, spojování a řazení dat. Index výrazně zrychluje čtení díky uspořádané struktuře (typicky B-tree), ale zpomaluje zápisy (INSERT/UPDATE/DELETE), zvyšuje nároky na úložiště a vyžaduje pravidelnou údržbu. Optimalizace dotazů proto vždy vyvažuje mezi latencí čtení, propustností zápisu, velikostí dat a složitostí správy.
Základní typy indexů a kdy je použít
- B-tree – univerzální volba pro testy rovnosti (
=), rozsahy (>, >=, <, <=),ORDER BYa prefixovéLIKE 'abc%'. - Hash – extrémně rychlé pro rovnost, bez podpory rozsahů a řazení (v PostgreSQL pouze pro rovnosti).
- GiST/GiN/BRIN (PostgreSQL) – speciální indexy pro fulltextové vyhledávání (GIN), podobnostní vyhledávání a geografické údaje (GiST), velmi velké tabulky s lokalitou dat (BRIN).
- Clustered vs. non-clustered – v InnoDB je primární klíč clusterovaný a fyzicky organizuje tabulku. SQL Server umožňuje definovat, který index je clusterovaný.
- Pokrývající (covering) – index, který obsahuje všechna pole potřebná pro dotaz, takže není nutné přistupovat k samotné tabulce (SQL Server:
INCLUDE; PostgreSQL:INCLUDEod verze 11; InnoDB nepodporujeINCLUDE, ale sekundární indexy nesou primární klíč, což může pomoci).
Volba sloupců a pořadí v kompozitním indexu
V kompozitním indexu rozhoduje pořadí sloupců. Platí pravidlo „left-prefix“: INDEX(a, b, c) pokryje dotazy, které filtrují podle a, (a, b) nebo (a, b, c). Pořadí volíme podle selektivity, tedy kolik řádků daný filtr vyloučí, a podle typických predikátů a řazení:
- Pro dotaz
WHERE country = ? AND created_at >= ?je často vhodný index(country, created_at), pokud se nejprve filtruje země a následně časový rozsah. - Pro stránkování s
ORDER BY created_at DESCzvažte index(created_at DESC)(PostgreSQL podporuje směr v indexu), případně klíčové stránkování (viz níže).
Sargabilita: napište dotaz tak, aby bylo možné použít index
Sargabilní predikát (Search ARGument ABle) umožní plánovači využít index. Vyhněte se transformacím sloupců v WHERE, které zabrání použití indexu:
- Špatně:
WHERE LOWER(email) = 'a@b.com'– raději vytvořte funkční/index výrazu (expression index) nebo uložte normalizovanou hodnotu. - Špatně:
WHERE created_at + INTERVAL '1 day' > NOW()– přepište naWHERE created_at > NOW() - INTERVAL '1 day'. - Špatně:
WHERE price * 1.21 >= 100– použijteWHERE price >= 100/1.21.
Indexy pro spojování, řazení a agregace
- JOIN – indexujte cizí klíče na obou stranách spojení (
FKsloupce v child tabulce;PK/UKv parent). Zlepší se výkon hash, merge i nested-loop joinů. - ORDER BY – index ve správném pořadí umožní „index-only sort“, jinak dochází k externímu třídění s náklady na CPU a I/O.
- GROUP BY – některé engine umí využít uspořádání indexu (např.
GROUP BY a, bs indexem(a, b)) pro efektivnější agregaci.
Parciální/filtrující a funkční indexy
Omezte index na relevantní podmnožinu dat, čímž ušetříte místo i náklady na zápis.
- Parciální index (PostgreSQL):
CREATE INDEX ... ON orders(created_at) WHERE status = 'PAID'; - Filtered index (SQL Server):
CREATE INDEX ... ON dbo.Orders(created_at) WHERE status = 'PAID'; - Index výrazu (PostgreSQL):
CREATE INDEX ... ON users ((LOWER(email)));
Fulltext, podobnost a „LIKE“
- Prefixové LIKE (
name LIKE 'abc%') – využívá B-tree (za podmínky kompatibilní kolace). - Obsahuje (
%abc%) – vyžaduje speciální index (PostgreSQLpg_trgm+ GIN/GiST) nebo fulltextové indexy (TSVector/TSQuery, InnoDB FULLTEXT). - Kolace a case-insensitive – zvolte konzistentní kolaci a typ, např. PostgreSQL
CITEXTnebo funkční index sLOWER().
Statistiky, kardinalita a plánovač
Plánovač (optimalizátor) odhaduje náklady na základě statistik – histogramů, hustoty a korelace sloupců. Nepřesné odhady vedou ke špatným plánům (např. nested-loop místo hash joinu).
- Pravidelně aktualizujte statistiky (PostgreSQL
ANALYZE, autovacuum; SQL ServerUPDATE STATISTICS; MySQL aktualizuje automaticky podle heuristik). - Zvyšte granularitu statistik (PostgreSQL
ALTER TABLE ... ALTER COLUMN ... SET STATISTICS n;). - V PostgreSQL používejte extended statistics (
CREATE STATISTICS ... (dependencies, mcv, ndistinct) ON (a, b);) pro korelované sloupce.
EXPLAIN/EXPLAIN ANALYZE: jak číst plán
Začněte příkazem EXPLAIN pro strukturu plánu a použijte EXPLAIN ANALYZE pro získání skutečných časů a počtu řádků. Sledujte:
- Rozdíl mezi Rows (odhady) a Actual Rows (skutečnost) – velké odchylky naznačují problémy se statistikami.
- Operace s nejvyššími náklady a časy – často jde o Seq Scan na velké tabulce, Sort bez indexu nebo Hash Aggregate na velkém množství řádků.
- Rozlišujte Filter a Index Cond – ideálně by měl být predikát v Index Cond (sargabilní), ne až jako filtr po načtení dat.
Optimalizace dotazů: přepisy, které mění plán
- Omezte
SELECT *– menší šířka řádku snižuje I/O a zvyšuje pravděpodobnost index-only scan. - CTE – v PostgreSQL od verze 12 jsou inline (predikáty mohou „propadnout“ dovnitř). Starší verze materializovaly CTE, což někdy bylo zbytečné.
- Eliminujte zbytečné DISTINCT – to přidává sort či agregaci. Pokud je unikátnost zajištěna schématem, je
DISTINCTnadbytečný. - Window funkce – používejte s vhodným
PARTITION BY/ORDER BYa odpovídajícími indexy pro minimalizaci třídění. - Predicate pushdown – filtrujte co nejdříve; u pohledů nebo sub-dotazů zajistěte propagaci filtrů.
Stránkování: od OFFSET k keyset paginaci
OFFSET je nákladné lineárně, protože server přeskočí N řádků. Preferujte keyset pagination:
- Místo
ORDER BY created_at DESC LIMIT 50 OFFSET 10000použijteWHERE created_at < poslední_hodnotas indexem(created_at DESC). - Zajistěte stabilní pořadí sekundárním klíčem (např.
(created_at DESC, id DESC)).
Particionování a indexy
Horizontální particionování (range/list/hash) zmenšuje pracovní sadu dat pro dotaz. Každá partition má vlastní indexy; díky pruningu se odstraní nerelevantní části dat.
- Zvolte klíč particionování podle přístupových vzorů (např. časové rozsahy).
- Vytvářejte identické indexy na všech partition nebo je deklarujte nad nadřazenou tabulkou, aby se propagovaly.
Údržba indexů a tabulek
- Fragmentace – udržujte vhodný fillfactor, periodicky provádějte REINDEX (PostgreSQL) nebo defragmentujte pomocí
ALTER INDEX REORGANIZE/REBUILD(SQL Server). - VACUUM/Autovacuum (PostgreSQL) – odstraňuje mrtvé tuple; bez něj se indexy a tabulky zvětšují a zpomalují.
- Odstraňujte nepoužívané indexy – sledujte dynamické pohledy a statistiky (DMV/pg_stat_*) – každý index zpomaluje zápisy.
Vliv enginu: PostgreSQL vs. InnoDB vs. SQL Server
- PostgreSQL – MVCC s viditelností v indexech, HOT aktualizace, široká nabídka indexů (GIN/GiST/BRIN),
INCLUDE, parciální a funkční indexy, pokročilé statistiky. - MySQL InnoDB – clusterovaný primární klíč, sekundární indexy nesou primární klíč; preferujte krátké a stabilní primární klíče (int/bigint), jinak jsou sekundární lookupy dražší.
- SQL Server – bohaté možnosti pokrývajících indexů (
INCLUDE), filtered indexes, columnstore pro analytické workloady, paralelismus a problematika „parameter sniffing“ (viz níže).
Parameter sniffing, plánová cache a bind parametry
Optimalizátor může volit plán na základě prvních parametrů uložených v cache. Pro rozdílné distribuční vzory dat to nemusí být optimální.
- SQL Server – zvažte OPTION (RECOMPILE), OPTIMIZE FOR nebo plan guides u problematických dotazů.
- PostgreSQL – rozlišuje „generic vs. custom“ plány u připravených dotazů; někdy je vhodné nepoužívat prepared statements pro velmi nerovnoměrná data.
- MySQL – menší vliv; doporučuje se psát dotazy s bind parametry (bez stringové konkatenace) pro bezpečnost a lepší plánování.
Agregace a analytika: B-tree vs. Columnstore
Pro reporting a OLAP zvažte sloupcové indexy a úložiště (SQL Server columnstore, PostgreSQL cstore_fdw nebo externí analytické systémy). U čistě OLTP aplikací však zůstává B-tree dominantní volbou.
Integrita a indexy: PK, FK, unique
- PK – implicitně indexovaný; v InnoDB určuje fyzické uspořádání tabulky.
- UNIQUE – zajišťuje integritu a často zrychluje vyhledávání podle jedinečných klíčů.
- FK – doporučuje se indexovat sloupce cizích klíčů v child tabulkách, jinak jsou operace mazání a aktualizace v parent tabulce nákladné.
Indexování JSON a polostrukturovaných dat
- PostgreSQL –
GINnajsonbpro klíč/hodnotu, funkční indexy na extrahované cesty (((jsonb_col ->> 'key'))). - MySQL – funkční indexy na
JSON_EXTRACT(...)(verze 8.0 a novější). - SQL Server – computed columns a indexy nad nimi.
Řízení zápisové zátěže a kompromisy
- Minimalizujte počet indexů u tabulek s vysokým zápisovým zatížením.
- Logicky rozdělte workload na „hot“ a „cold“ data, využijte archivační strategie.
- Batch operace (bulk load) provádějte s dočasným vypnutím nebo odložením indexů (pokud to engine umožňuje) a následným rebuildem.
Bezpečnost a multitenance dopady na plánování
Row-level security, pohledy a politické filtry mohou znemožnit predicate pushdown. U multitenantních aplikací indexujte tenant_id jako první klíč v kompozici s dalšími filtry pro efektivní prunování dat.
Praktické návrhové vzory
- „Horký čas“: posledních několik dní – kompozitní index
(created_at DESC, id DESC)pro rychlé načítání feedů. - Filtrování podle stavu a času:
(status, updated_at)plus parciální indexy pro nejčastější stavy. - Auditní log: BRIN index na čas pro masivní tabulky, doplněný o pravidelné vacuum a archivační partitioning.
- Adresář/uživatelé