Indexy v MySQL – praktické ukázky
- 6. Březen 2009
- Publikováno v MySQL
- Napište komentář
O indexech v MySQL bylo napsáno už spousta článků. Převážná většina se však zabývá pouze teorií. Rozhodl jsem se tedy připravit pár ukázek, jak indexy ovlivní rychlost provádění dotazů nad daty. Abych se vyhl srovnávání s ostatními články co do teoretické obsáhlosti, zredukuji teorii takřka na nulu. Z naměřených čísel si můžete vyvodit závěry sami, případně si můžete přečíst (na pohled trochu delší) zhodnocení testů na konci článku.
Teoretické minimum
Vytvořit obyčejný index lze několik způsoby. Buďto úpravou stávající tabulky, vytvořením samostatného indexu na tabulce, nebo přímo při vytváření tabulky:
CREATE INDEX nazev_indexu ON tabulka ( sloupec );
CREATE TABLE tabulka ( sloupec datový_typ, KEY nazev_indexu ( sloupec ) );
Odstranit index lze taktéž více způsoby:
DROP INDEX nazev_indexu ON tabulka ( sloupec );
Dost bylo teorie
Na řadě jsou slibované ukázky, jak indexy ovlivní rychlost práce nad tabulkami.
Příprava dat
Vytvořil jsem si tabulku test0 o třech sloupcích:
id INT(11) NOT NULL AUTO_INCREMENT,
retezec VARCHAR(10) NOT NULL,
cislo INT(6) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
kterou jsem jsem naplnil 4. miliony záznamy, kde atribut 'retezec' obsahuje náhodně vygenerované řetězce délky 10, složené z malých písmen abecedy. Atribut 'cislo' pak obsahuje náhodná čísla od 0 do 999999.
Aby bylo snadné porovnávat časy operací s indexy a bez indexů, zkopíroval jsem si celou tabulku na test1 a test2. Respektive jsem si vytvořil další tabulky se stejnou strukturou jako test0 a překopíroval data:
SELECT * FROM test0;
V tabulce test1 jsem vytvořil index na sloupci 'cislo' a v tabulce test2 index na sloupci 'cislo' i 'retezec':
ALTER TABLE test2 ADD INDEX icislo (cislo);
ALTER TABLE test2 ADD INDEX iretezec (retezec);
Vše je připraveno pro samotné testování.
Testování
Hlavní důvod proč používat indexy je rychlost vyhledávání. Začneme tedy s příkazem SELECT.
SELECT
I. Pro začátek vyhledáme záznamy dle jedné hodnoty atributu 'cislo'.
+---------+------------+--------+
| id | retezec | cislo |
+---------+------------+--------+
| 2330500 | ujuojfsinz | 735473 |
| 3504264 | omjmoevlxd | 735473 |
| 3650661 | xggdepckxv | 735473 |
+---------+------------+--------+
3 rows in SET (0.84 sec)
Stejný dotaz provedu nad tabulkou test1 a test2. Kvůli přesnějším výsledkům je dotaz spuštěn 10x a jako výslednou hodnotu beru jejich průměr.
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 0.828719 | 0.888683 | 0.8443537 |
| test1 | 0.000271 | 0.000608 | 0.0005387 |
| test2 | 0.000522 | 0.000666 | 0.000583 |
II. Nyní zkusíme načíst záznamy podle intervalu hodnot atributu 'cislo'. V případě mých dat jde o 2.445.909 záznamů, což je přibližně 61,2% celé tabulky.
WHERE cislo BETWEEN 263467 AND 874684;
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 2.27370 | 2.462875 | 2.341735 |
| test1 | 2.271746 | 2.57882 | 2.345748 |
| test2 | 2.216138 | 2.62435 | 2.295494 |
III. Zkusíme interval zúžit. Nyní dotaz vybere 396.398 záznamů, tedy zhruba 10% celé tabulky.
WHERE cislo BETWEEN 723658 and 822764;
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 1.037267 | 1.1911230 | 1.082266 |
| test1 | 1.431868 | 1.685166 | 1.465738 |
| test2 | 1.442451 | 1.491846 | 1.462038 |
IV. Zúžíme interval ještě více, na 199.967 záznamů, tedy 5% všech záznamů.
WHERE cislo BETWEEN 757838 and 807875;
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 0.914104 | 0.958660 | 0.922324 |
| test1 | 0.728407 | 0.808515 | 0.728407 |
| test2 | 0.722438 | 0.954074 | 0.723137 |
V. V dalším testu zkusíme výběr podle sloupců 'cislo' i 'retezec'.
-> WHERE retezec LIKE 'mxpr%' AND
-> cislo BETWEEN 500000 AND 900000;
+---------+------------+--------+
| id | retezec | cislo |
+---------+------------+--------+
| 285727 | mxprvgpxao | 815039 |
| 529258 | mxprbfndec | 855491 |
| 1136965 | mxprstrlmx | 514873 |
| 2813857 | mxprbpxhww | 851652 |
| 3693942 | mxprvgpxao | 705005 |
+---------+------------+--------+
5 rows in SET (1.03 sec)
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 0.965487 | 1.066500 | 1.013356 |
| test1 | 0.987207 | 1.080257 | 1.041164 |
| test2 | 0.000810 | 0.000940 | 0.000877 |
VI. Na konec ještě test s agregační funkcí. Pro jednoduchost jen COUNT() pro zjištění počtu různých hodnot atributu 'cislo'. Kvůli omezení výstupu je použita podmínka, že počet hodnot ve skupině musí být větší jak 14.
GROUP BY cislo HAVING pocet> 14;
+--------+-------+
| cislo | pocet |
+--------+-------+
| 46018 | 15 |
| 292161 | 15 |
| 331827 | 15 |
| 407797 | 15 |
| 461206 | 16 |
| 731575 | 15 |
| 734222 | 15 |
| 787101 | 16 |
| 810976 | 15 |
| 847239 | 15 |
| 952733 | 15 |
| 959964 | 15 |
+--------+-------+
12 rows in SET (1 min 20.25 sec)
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 78.336391 | 127.023564 | 101.273832 |
| test1 | 1.306242 | 1.410534 | 1.349165 |
| test2 | 1.275477 | 1.354496 | 1.315935 |
INSERT
I. Vložení deseti různých záznamů v deseti dotazech.
INSERT INTO test0 (retezec,cislo) VALUES ('ugpiecujya',79433);
INSERT INTO test0 (retezec,cislo) VALUES ('znynhczwoe',603331);
INSERT INTO test0 (retezec,cislo) VALUES ('dsiagzlkze',747203);
INSERT INTO test0 (retezec,cislo) VALUES ('vtrmlmtbgl',293059);
INSERT INTO test0 (retezec,cislo) VALUES ('dzevllqzuq',41532);
INSERT INTO test0 (retezec,cislo) VALUES ('twikyivfrj',323563);
INSERT INTO test0 (retezec,cislo) VALUES ('nriqlnkdoi',927033);
INSERT INTO test0 (retezec,cislo) VALUES ('kiwdycjomu',670317);
INSERT INTO test0 (retezec,cislo) VALUES ('vyhojxuvnm',947196);
| Tabulka | Minimum [s] | Maximum [s] | Průměr [s] |
| test0 | 0.000404 | 0.000547 | 0.000443 |
| test1 | 0.000261 | 0.093289 | 0.036356 |
| test2 | 0.052013 | 0.094129 | 0.072772 |
II. Vložení deseti různých záznamů pomocí jednoho dotazu.
('rdscmskoub', 159076), ('fhueoucyha', 919515),
('dgmjmtvvpl', 416806), ('tobenuttbh', 772246),
('rwfuuflkwf', 376395), ('jbxukrcmju', 625428),
('rvwwjcnkdz', 755552), ('pyheanioit', 344962),
('xdczjngwmc', 289732), ('ocmrfyvhqt', 653739);
| Tabulka | Doba provádění [s] |
| test0 | 0.000681 |
| test1 | 0.242820 |
| test2 | 0.293349 |
UPDATE
Aktualizace dat podle podmínek na dva atributy.
WHERE retezec LIKE 'kjs%' AND cislo> 800000;
Query OK, 44 rows affected (11.24 sec)
Rows matched: 44 Changed: 44 WARNINGS: 0
mysql> UPDATE test1 SET cislo = 0
WHERE retezec LIKE 'kjs%' AND cislo> 800000;
Query OK, 44 rows affected (12.19 sec)
Rows matched: 44 Changed: 44 WARNINGS: 0
mysql> UPDATE test2 SET cislo = 0
WHERE retezec LIKE 'kjs%' AND cislo> 800000;
Query OK, 44 rows affected (0.30 sec)
Rows matched: 44 Changed: 44 WARNINGS: 0
| Tabulka | Doba provádění [s] |
| test0 | 11.247501 |
| test1 | 12.191598 |
| test2 | 0.300179 |
Pokus o update všech záznamů v tabulce s indexy končí mnohaminutovým zpracováváním. Kolik přesně Vám nepovím, neměl jsem ty nervy tak dlouho čekat.
DELETE
Mazání záznamů dle atributu 'cislo'.
Query OK, 4 rows affected (1.45 sec)
mysql> DELETE FROM test0 WHERE cislo = 87364;
Query OK, 3 rows affected (1.10 sec)
mysql> DELETE FROM test0 WHERE cislo = 123456;
Query OK, 3 rows affected (0.95 sec)
| Tabulka | Číslo 998051 [s] | Číslo 87364 [s] | Číslo 123456 [s] |
| test0 | 1.399951 | 1.099434 | 0.951632 |
| test1 | 0.340928 | 0.219564 | 0.228158 |
| test2 | 0.464200 | 0.295632 | 0.346192 |
Mazání záznamů dle rozmezí hodnot atributu 'cislo'.
WHERE cislo BETWEEN 287365 AND 421831;
Query OK, 538448 rows affected (1 min 5.98 sec)
mysql> DELETE FROM test1
WHERE cislo BETWEEN 287365 AND 421831;
Query OK, 538448 rows affected (2 hours 19 min 27.76 sec)
Sami vidíte, že čas provádění nad tabulkou test1 s indexem na atributu 'cislo' je hodně nepraktický. V takovéhle situaci je lepší indexy zrušit, provést požadovanou modifikaci dat a indexy znovu vytvořit.
Query OK, 4000038 rows affected (27.35 sec)
Records: 4000038 Duplicates: 0 WARNINGS: 0
mysql> ALTER TABLE test2 DROP INDEX iretezec;
Query OK, 4000038 rows affected (27.64 sec)
Records: 4000038 Duplicates: 0 WARNINGS: 0
mysql> DELETE FROM test2
WHERE cislo BETWEEN 287365 AND 421831;
Query OK, 538448 rows affected (1 min 22.62 sec)
mysql> ALTER TABLE test2 ADD INDEX icislo (cislo);
Query OK, 3461590 rows affected (33.71 sec)
Records: 3461590 Duplicates: 0 WARNINGS: 0
mysql> ALTER TABLE test2 ADD INDEX iretezec (retezec);
Query OK, 3461590 rows affected (1 min 1.72 sec)
Records: 3461590 Duplicates: 0 WARNINGS: 0
Celkem 3 min 53.04 sec.
Zhodnocení testů
Hned v testu SELECT I. vidíme silnou stránku indexů. Tedy vyhledávání malého počtu záznamů. Zrychlení si lze všimnout také u testu SELECT IV., kdy je příkazem vybráno cca 5% všech záznamů. Pokud je výsledný počet záznamů příliš vysoký (SELECT II.), SŘBD se před samotným vyhledáváním rozhodne index nepoužít. Proto jsou v tomto testu shodné časy nad všemi tabulkami. Je-li počet záznamů odpovídající podmínce tak nešťastný, že je index použit v nepravou chvíli, může dojít dokonce ke zpomalení vyhledávání pomocí indexů, jako se stalo při testu SELECT III.. Obrovské zrychlení nastane ve chvíli, kdy použijeme agregační funkci na sloupec s indexem. V indexovém souboru jsou totiž uloženy hodnoty atributu 'cislo' seřazeně, proto je zjištění jejich počtu znatelně rychlejší, než procházení všech dat (SELECT VI.).
Při vkládání se však již projeví stinná stránka indexů. Tedy zpomalení při všech operacích (záleží na složitosti použitého příkazu a množství ovlivněných dat) krom SELECTU. Indexy je totiž nutno udržovat aktuální a tedy při každé změně dat se upravují. Při testech INSERT I. a II. s přehledem vedou operace nad tabulkou test0. Nad ostatními tabulkami je potřeba udržovat indexy, a čím více jich je, tím se čas protahuje. Lze si také všimnout, že v jistých situacích je vhodnější použít jeden větší dotaz, než více malých.
U testu UPDATE se příkaz vyhodnocuje nejprve podle podmínky retezec LIKE 'kjs%', tedy je použit index na sloupci 'retezec'. Následný výběr dle cislo > 800000 je již prováděn z malého množství záznamu a tak se index na sloupci 'cislo' neuplatní. Proto lze také sledovat zpomalení na tabulce s indexem pouze na sloupci 'cislo'. Po provedení příkazu je totiž třeba index aktualizovat. Oproti tomu na tabulce test2 je vidět urychlení, za které je zodpovědný index na sloupci 'retezec'. Ten napomohl nalezení záznamů, které mají být ovlivněny.
Pokud se pomocí UPDATE či DELETE snažíme ovlivnit velké množství dat, je výsledek nad tabulkami s indexy žalostný, jak lze videt na testu DELETE. V takové situaci může být přijatelným řešením indexy dočasně zrušit, provést modifikaci dat a opět indexy vytvořit. Časová úspora je pak obrovská.
Závěr
Idexy se určitě vyplatí používat, je však třeba dobře zvážit, na které sloupce je použít. Již při návrhu struktury databáze můžeme mít představu jaké operace a s jakým množstvím dat budeme nad tabulkami provádět. Zvolit indexy na základě takové úvahy je jistě lepší, než je bez rozmysli naflákat na půlku tabulky.
Dosud žádný komentář.