sql >> Databáze >  >> RDS >> Mysql

MySQL MyISAM pomalý dotaz count() navzdory pokrytí indexu

Tady je to, co se děje.

The SELECT COUNT (...) icd_index where icd='25000'

použije index, což je strom BTree oddělený od dat. Ale skenuje to tímto způsobem:

  1. Najděte první položku s icd='25000'. To je téměř okamžité.
  2. Skenujte dopředu, dokud nenajde změnu v icd. Tím se naskenuje pouze index, data se nedotknou. Podle EXPLAIN bude k naskenování asi 910 104 položek rejstříku.

Nyní se podívejme na BTree pro tento index. Na základě polí v indexu bude mít každý řádek přesně 22 bajtů, plus bude nějaká režie (odhad 40 %). Indexový blok MyISAM je 1 kB (srovnej 16 kB InnoDB). Odhadoval bych 33 řádků na blok. 910,104/33 říká, že k provedení COUNT je potřeba přečíst asi 27 kB bloků. (Poznámka:COUNT(core_id) potřebuje zkontrolovat core_id protože je null, COUNT(*) ne; to je drobný rozdíl.) Čtení 27K bloků na obyčejném pevném disku trvá asi 270 sekund. Měli jste štěstí, že jste to zvládli za 60 sekund.

Druhý běh našel všechny tyto bloky v key_buffer (za předpokladu, že key_buffer_size je alespoň 27 MB), takže nemusel čekat na disk. Proto to bylo mnohem rychlejší. (Toto ignoruje mezipaměť dotazů, kterou jste měli rozumně vyprázdnit nebo použít SQL_NO_CACHE.)

5.6 je náhodou irelevantní (ale díky za zmínku), protože tento proces se od verze 4.0 nebo dříve nezměnil (kromě toho, že utf8 neexistovalo; více o tom níže).

Přechod na InnoDB by pomohl několika způsoby. PRIMÁRNÍ KLÍČ by byl „seskupován“ s daty, nikoli uložen jako samostatný strom BTree. Jakmile jsou tedy data nebo PK uložena do mezipaměti, ostatní jsou okamžitě k dispozici. Počet bloků by byl spíše 5K, ale byly by to bloky 16KB. Tyto mohou být načteny rychleji, pokud je mezipaměť studená.

Ptáte se „Potřebuji index pouze na icd?“ – No, to by zmenšilo velikost MyISAM BTree na asi 21 bajtů na řádek, takže BTree by bylo asi 21/27 velikosti, což není příliš zlepšení (alespoň pro situace studené mezipaměti).

Další myšlenka je, pokud icd je vždy číselné a vždy číselné, chcete-li použít MEDIUMINT UNSIGNED a připněte na ZEROFILL pokud může mít úvodní nuly.

Jejda, nevšiml jsem si CHARACTER SET. (Výše uvedená čísla jsem opravil, ale dovolte mi to upřesnit.)

  • CHAR(5) umožňuje 5 znaků .
  • ascii zabírá 1 bajt za znak .
  • utf8 zabírá až 3 bajty za znaků .
  • Takže CHAR(5) CHARACTER SET utf8 zabere 15 bajtů vždy .

Změna sloupce na CHAR(5) CHARACTER SET ascii by se zmenšil na 5 bajtů.

Změna na MEDIUMINT UNSIGNED ZEROFILL by jej zmenšila na 3 bajty.

Zmenšení dat by zrychlilo I/O o zhruba úměrnou hodnotu (po povolení dalších 6 bajtů pro další dvě pole.



  1. PostgreSQL:Jak zjistit chybějící čísla ve sloupci pomocí create_series()?

  2. Převést/zakódovat řetězec na čísla

  3. MySQL:jak provést zabezpečení na úrovni řádků (jako je virtuální privátní databáze Oracle)?

  4. SCD typ 2