Při práci s frameworkem Ruby on Rails obvykle pracujeme s relačními databázemi, jako je MySQL nebo PostgreSQL. Při definování migrací pomocí Active Record Migrations se setkáváme s tzv. indexy, ale začátečníci často indexům zcela nerozumí a neví, jaké výhody přinášejí.
Při práci s Ruby na adrese Rails se obvykle zabýváme relačními databázemi, jako jsou např. MySQL nebo PostgreSQL. Při definování migrací pomocí Active Record Migrations se setkáváme s tzv. indexy, ale začátečníci často indexům zcela nerozumí a neví, jaké výhody přinášejí.
V tomto příspěvku bych rád vysvětlil, co jsou to indexy, k čemu se používají, a uvedl několik osvědčených postupů, jak je používat.
Databáze
Existuje mnoho databázových strojů a mezi nejoblíbenější patří již zmíněné MySQL, PostgreSQL, Oracle nebo Microsoft SQL Server. Všechny jsou relačními databázemi, což znamená, že všechna data jsou vzájemně propojena a uložena v tabulkách. Každý řádek tabulky se nazývá záznam a každý má svůj jedinečný identifikátor (id). Žebříček nejoblíbenějších databázových strojů si můžete prohlédnout na adrese https://db-engines.com/en/ranking. Najdete tam také některé nerelační databáze, například MongoDB.
Vytvoření indexu
Tabulky v našich databázích mohou mít jen několik málo až několik desítek - v extrémních případech až několik set - sloupců. Mějte na paměti, že každá tabulka může mít neomezený počet řádků. Tento počet nevyplývá přímo ze struktury databáze a vždy bychom měli počítat s tím, že počet záznamů bude postupně narůstat a v důsledku toho bude růst i naše databáze. Počáteční předpoklady a dotazy napsané ve stávajících aplikacích mohou být skvělé pro malý nebo střední počet záznamů, ale postupem času, když přibývá dat, přestává být komunikace aplikace s databází efektivní.
Úkolem programátora je napsat dotaz, který získá určitá data z tabulky nebo tabulek, ale optimální způsob zpracování dotazu závisí na databázovém stroji. Nezapomeňte, že databázové stroje načítají data z disku do paměti a poté je prohledávají. To znamená, že pokud mnoho uživatelů provádí složité operace najednou, několik z nich bude muset čekat, až na ně přijde řada, protože na realizaci jejich vyhledávání nebudou stačit prostředky. Proto jsou tak důležité relevantní indexy.
Wiki: Index - datová struktura, která zvyšuje rychlost vyhledávání v tabulce.
Pro každý index je třeba definovat klíče (pro jeden nebo více sloupců), které budou použity k vyhledávání záznamů v tabulce. Data v indexu budou seřazena podle předem definovaného klíče, což výrazně urychlí vyhledávání dat v tabulce. Nejjednodušším příkladem z běžného života je telefonní seznam, ve kterém jsou lidé seřazeni podle jména a příjmení. Lze říci, že naším indexem v tomto případě bude jméno a příjmení.
Jak vybrat nejlepší indexový klíč? Není to složité - stačí si zapamatovat několik pravidel. Vytvořte index založený na sloupcích, které:
- bude často používán v našich dotazech (KDE),
- ve vzájemné kombinaci dávají jedinečnou hodnotu (tj. hodnotu, která označuje přesně jeden řádek),
- budou použity jako tzv. spojovací sloupce (JOIN),
- poskytují nejselektivnější klíče, tj. ty, které při psaní dotazu vracejí nejmenší počet řádků.
Pokud již víme, které klíče budou pro naši tabulku optimální, můžeme si také položit otázku, kolik indexů potřebujeme. V tomto případě je nejlepší znát dotazy, které se budou vztahovat k naší tabulce, již ve fázi návrhu.
Vytvořme indexy pro konkrétní dotazy, které se budou zobrazovat, ale nepišme je pro každý sloupec. Indexy, stejně jako tabulky, musí být někde uloženy, takže když vytváříme tabulky s indexem pro každý sloupec, musíme počítat s tím, že se může výrazně zvětšit množství zabraného místa.
Vytvoření jedinečného indexu
Dalším problémem, na který je třeba myslet, je jedinečnost. Vyplatí se věnovat pět minut navíc přemýšlení o tom, zda je náš index skutečně jedinečný. Tímto způsobem optimalizátoru dotazu sdělíme, že nemusí očekávat duplicity v dotazu. Například e-mailové adresy:
frozenstringliteral: true
třída CreateUsers < ActiveRecord::Migration[6.0]
def change
createtable :users do |t|
t.string :email, null: false
end
addindex :users, :email, unique: true
end
end
Na příkladu motoru PostgreSQL ukážu rozdíl v rychlosti dotazu na sloupec e-mail s jedinečným indexem a bez indexu.
1. Můžete použít vzorek kód úryvky ve vlastní databázi, abyste mohli otestovat níže uvedený příklad. Nejprve vytvoříme prázdnou tabulku s jedním sloupcem:
CREATE TABLE users (
email varchar
);
2. Pro test vygenerujeme 10 000 záznamů:
DO $
BEGIN FOR i IN 1..10000 LOOP
INSERT INTO users values((select 'user' || i || '@example.com'));
END LOOP; END;
$;
Pomocí nástroje EXPLAIN ANALYZE ověříme, jak rychle bude náš dotaz zpracován, když chceme v databázi najít konkrétního uživatele.
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Náš dotaz si vynutil iteraci kolem celé tabulky při hledání záznamu, který zajímá. nás.
Tento proces se nazývá sekvenční skenování. V tomto případě je nejvhodnějším způsobem čtení celé tabulky a filtrování jednotlivých řádků.
PostgreSQL odfiltruje nepotřebné řádky a vrátí pouze ty, které nás zajímají. To je v tomto případě opravdu to nejlepší, co lze udělat. Sekvenční skenování není vždy špatné, jsou případy, kdy je sekvenční skenování ideální.
4. Nyní je čas zkontrolovat již provedený dotaz na tabulku, která má INDEX UNIQUE. Nastavíme index a provedeme dotaz.
EATE UNIQUE INDEX index_email na users(email);
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user890example.com';
Tentokrát PostgreSQL využila skenování indexů, protože všechny potřebné sloupce jsou již v indexu.
Výběr pouze několika řádků je při použití indexu velmi efektivní. Pokud je však vybráno více dat, bude prohledávání indexu a tabulky příliš časově náročné.
Souhrn
Jak vidíte, čas provedení dotazu na sloupec s indexem je mnohem kratší (v uvedeném příkladu se jedná o pokles z 1,267 ms na 0,111 ms, tedy až 91,24%!). Nejdůležitějším rozdílem je způsob, jakým PostgreSQL vyhledává záznam, který nás zajímá. V prvním případě musel databázový stroj hledat celý záznam, který jsme potřebovali, v celé tabulce. V druhém případě je však struktura indexu setříděná a unikátní, tudíž engine věděl, kde se záznam nachází, což výrazně urychlilo dobu zpracování dotazu.
V případě rozsáhlých databází a velmi složitých dotazů mohou správně nastavené indexy výrazně urychlit práci vaší aplikace, aniž by bylo nutné zvyšovat rychlost stroje, na kterém v databázi prohledáváte.
Je třeba si uvědomit, že vytváření indexů pro každý sloupec není dobrý postup. Vytvořené indexy sice urychlí práci optimalizátoru při vyhledávání zajímavých dat, ale zároveň zpomalí vkládání nových a aktualizaci stávajících.