sql >> Databáze >  >> RDS >> MariaDB

Maximalizace účinnosti databázového dotazu pro MySQL – část druhá

Toto je druhá část dvoudílného blogu o maximalizaci efektivity databázových dotazů v MySQL. První část si můžete přečíst zde.

Použití jednosloupcového, složeného, ​​předponového a krycího indexu

Tabulky, které často zaznamenávají vysoký provoz, musí být správně indexovány. Není důležité pouze indexovat tabulku, ale také musíte určit a analyzovat, jaké typy dotazů nebo typy načítání potřebujete pro konkrétní tabulku. Důrazně se doporučuje, abyste analyzovali, jaký typ dotazů nebo načítání dat potřebujete pro konkrétní tabulku, než se rozhodnete, jaké indexy jsou pro tabulku vyžadovány. Pojďme se podívat na tyto typy indexů a na to, jak je můžete použít k maximalizaci výkonu dotazů.

Jednosloupcový index

Tabulka InnoD může obsahovat maximálně 64 sekundárních indexů. Jednosloupcový index (nebo celosloupcový index) je index přiřazený pouze ke konkrétnímu sloupci. Dobrým kandidátem je vytvoření indexu pro určitý sloupec, který obsahuje odlišné hodnoty. Dobrý index musí mít vysokou mohutnost a statistiky, aby mohl optimalizátor zvolit správný plán dotazů. Chcete-li zobrazit distribuci indexů, můžete zkontrolovat syntaxi SHOW INDEXES stejně jako níže:

root[test]#> SHOW INDEXES FROM users_account\G

*************************** 1. row ***************************

        Table: users_account

   Non_unique: 0

     Key_name: PRIMARY

 Seq_in_index: 1

  Column_name: id

    Collation: A

  Cardinality: 131232

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

*************************** 2. row ***************************

        Table: users_account

   Non_unique: 1

     Key_name: name

 Seq_in_index: 1

  Column_name: last_name

    Collation: A

  Cardinality: 8995

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

*************************** 3. row ***************************

        Table: users_account

   Non_unique: 1

     Key_name: name

 Seq_in_index: 2

  Column_name: first_name

    Collation: A

  Cardinality: 131232

     Sub_part: NULL

       Packed: NULL

         Null: 

   Index_type: BTREE

      Comment: 

Index_comment: 

3 rows in set (0.00 sec)

Můžete také zkontrolovat pomocí tabulek information_schema.index_statistics nebo mysql.innodb_index_stats.

Složené (složené) nebo vícedílné indexy

Složený index (běžně nazývaný složený index) je vícedílný index složený z více sloupců. MySQL umožňuje až 16 sloupců ohraničených pro konkrétní složený index. Překročení limitu vrátí chybu jako níže:

ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed

Složený index poskytuje podporu pro vaše dotazy, ale vyžaduje, abyste měli jasnou představu o tom, jak data získáváte. Například tabulka s DDL...

CREATE TABLE `user_account` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `last_name` char(30) NOT NULL,

  `first_name` char(30) NOT NULL,

  `dob` date DEFAULT NULL,

  `zip` varchar(10) DEFAULT NULL,

  `city` varchar(100) DEFAULT NULL,

  `state` varchar(100) DEFAULT NULL,

  `country` varchar(50) NOT NULL,

  `tel` varchar(16) DEFAULT NULL

  PRIMARY KEY (`id`),

  KEY `name` (`last_name`,`first_name`)

) ENGINE=InnoDB DEFAULT CHARSET=latin1

...který se skládá ze složeného indexu `název`. Složený index zlepšuje výkon dotazů, jakmile jsou tyto klíče referenční jako použité klíčové části. Viz například následující:

root[test]#> explain format=json select * from users_account where last_name='Namuag' and first_name='Maximus'\G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "1.20"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "60",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 1,

      "rows_produced_per_join": 1,

      "filtered": "100.00",

      "cost_info": {

        "read_cost": "1.00",

        "eval_cost": "0.20",

        "prefix_cost": "1.20",

        "data_read_per_join": "352"

      },

      "used_columns": [

        "id",

        "last_name",

        "first_name",

        "dob",

        "zip",

        "city",

        "state",

        "country",

        "tel"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec

Používané_klíčové_části ukazují, že plán dotazů dokonale vybral požadované sloupce zahrnuté v našem složeném indexu.

Kompozitní indexování má také svá omezení. Některé podmínky v dotazu nemohou mít všechny sloupce součástí klíče.

Dokumentace říká:"Optimalizátor se pokouší použít další klíčové části k určení intervalu, pokud je operátor porovnání =, <=> nebo IS NULL. Pokud je operátor> , <,>=, <=, !=, <>, BETWEEN nebo LIKE, optimalizátor je použije, ale nezohledňuje žádné další klíčové části. Pro následující výraz používá optimalizátor =z prvního srovnání. Také používá>=z druhého srovnání, ale nezohledňuje žádné další klíčové části a nepoužívá třetí srovnání pro konstrukci intervalu…“ . V zásadě to znamená, že bez ohledu na to, že máte složený index pro dva sloupce, ukázkový dotaz níže nepokrývá obě pole:

root[test]#> explain format=json select * from users_account where last_name>='Zu' and first_name='Maximus'\G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "34.61"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "range",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name"

      ],

      "key_length": "60",

      "rows_examined_per_scan": 24,

      "rows_produced_per_join": 2,

      "filtered": "10.00",

      "index_condition": "((`test`.`users_account`.`first_name` = 'Maximus') and (`test`.`users_account`.`last_name` >= 'Zu'))",

      "cost_info": {

        "read_cost": "34.13",

        "eval_cost": "0.48",

        "prefix_cost": "34.61",

        "data_read_per_join": "844"

      },

      "used_columns": [

        "id",

        "last_name",

        "first_name",

        "dob",

        "zip",

        "city",

        "state",

        "country",

        "tel"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec)

V tomto případě (a pokud váš dotaz obsahuje více rozsahů namísto konstantních nebo referenčních typů), nepoužívejte složené indexy. Jen to plýtvá vaší pamětí a vyrovnávací pamětí a zvyšuje to degradaci výkonu vašich dotazů.

Indexy předpon

Indexy předpon jsou indexy, které obsahují sloupce odkazované jako index, ale mají pouze počáteční délku definovanou pro tento sloupec a tato část (nebo data předpony) jsou jedinou částí uloženou ve vyrovnávací paměti. Indexy prefixů mohou pomoci zmenšit zdroje vaší vyrovnávací paměti a také místo na disku, protože nemusí zabírat celou délku sloupce. Co to znamená? Vezměme si příklad. Porovnejme dopad mezi indexem plné délky a indexem předpony.

root[test]#> create index name on users_account(last_name, first_name);

Query OK, 0 rows affected (0.42 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

36M     /var/lib/mysql/test/users_account.ibd

Vytvořili jsme úplný složený index, který spotřebovává celkem 36 MiB tabulkového prostoru pro tabulku users_account. Necháme to a pak přidáme index předpony.

root[test]#> drop index name on users_account;

Query OK, 0 rows affected (0.01 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> alter table users_account engine=innodb;

Query OK, 0 rows affected (0.63 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

24M     /var/lib/mysql/test/users_account.ibd






root[test]#> create index name on users_account(last_name(5), first_name(5));

Query OK, 0 rows affected (0.42 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/users_account.*

12K     /var/lib/mysql/test/users_account.frm

28M     /var/lib/mysql/test/users_account.ibd

Pomocí indexu prefixu pojme pouze 28 MiB, což je méně než 8 MiB než použití indexu plné délky. To je skvělé slyšet, ale to neznamená, že je to výkonné a slouží to, co potřebujete.

Pokud se rozhodnete přidat index předpony, musíte nejprve určit, jaký typ dotazu pro získávání dat potřebujete. Vytvoření indexu předpon vám pomůže využít větší efektivitu s fondem vyrovnávacích pamětí, a tak pomůže s výkonem dotazů, ale také musíte znát jeho omezení. Porovnejme například výkon při použití indexu plné délky a indexu předpony.

Pojďme vytvořit úplný index pomocí složeného indexu

root[test]#> create index name on users_account(last_name, first_name);

Query OK, 0 rows affected (0.45 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#>  EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "1.61"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "60",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 3,

      "rows_produced_per_join": 3,

      "filtered": "100.00",

      "using_index": true,

      "cost_info": {

        "read_cost": "1.02",

        "eval_cost": "0.60",

        "prefix_cost": "1.62",

        "data_read_per_join": "1K"

      },

      "used_columns": [

        "last_name",

        "first_name"

      ]

    }

  }

}

1 row in set, 1 warning (0.00 sec)



root[test]#> flush status;

Query OK, 0 rows affected (0.02 sec)



root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

PAGER set to 'cat -> /dev/null'

3 rows in set (0.00 sec)



root[test]#> nopager; show status like 'Handler_read%';

PAGER set to stdout

+-----------------------+-------+

| Variable_name         | Value |

+-----------------------+-------+

| Handler_read_first    | 0 |

| Handler_read_key      | 1 |

| Handler_read_last     | 0 |

| Handler_read_next     | 3 |

| Handler_read_prev     | 0 |

| Handler_read_rnd      | 0 |

| Handler_read_rnd_next | 0     |

+-----------------------+-------+

7 rows in set (0.00 sec)

Výsledek ukazuje, že ve skutečnosti používá krycí index, tj. "using_index":true a používá indexy správně, tj. Handler_read_key je inkrementován a provádí skenování indexu, jak je Handler_read_next inkrementováno.

Nyní zkusme použít předponový index stejného přístupu

root[test]#> create index name on users_account(last_name(5), first_name(5));

Query OK, 0 rows affected (0.22 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#>  EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

*************************** 1. row ***************************

EXPLAIN: {

  "query_block": {

    "select_id": 1,

    "cost_info": {

      "query_cost": "3.60"

    },

    "table": {

      "table_name": "users_account",

      "access_type": "ref",

      "possible_keys": [

        "name"

      ],

      "key": "name",

      "used_key_parts": [

        "last_name",

        "first_name"

      ],

      "key_length": "10",

      "ref": [

        "const",

        "const"

      ],

      "rows_examined_per_scan": 3,

      "rows_produced_per_join": 3,

      "filtered": "100.00",

      "cost_info": {

        "read_cost": "3.00",

        "eval_cost": "0.60",

        "prefix_cost": "3.60",

        "data_read_per_join": "1K"

      },

      "used_columns": [

        "last_name",

        "first_name"

      ],

      "attached_condition": "((`test`.`users_account`.`first_name` = 'Maximus Aleksandre') and (`test`.`users_account`.`last_name` = 'Namuag'))"

    }

  }

}

1 row in set, 1 warning (0.00 sec)



root[test]#> flush status;

Query OK, 0 rows affected (0.01 sec)



root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

PAGER set to 'cat -> /dev/null'

3 rows in set (0.00 sec)



root[test]#> nopager; show status like 'Handler_read%';

PAGER set to stdout

+-----------------------+-------+

| Variable_name         | Value |

+-----------------------+-------+

| Handler_read_first    | 0 |

| Handler_read_key      | 1 |

| Handler_read_last     | 0 |

| Handler_read_next     | 3 |

| Handler_read_prev     | 0 |

| Handler_read_rnd      | 0 |

| Handler_read_rnd_next | 0     |

+-----------------------+-------+

7 rows in set (0.00 sec)

MySQL odhaluje, že index používá správně, ale ve srovnání s plnohodnotným indexem je zde patrná režie nákladů. To je zřejmé a vysvětlitelné, protože index prefixů nepokrývá celou délku hodnot pole. Použití indexu prefixu není náhradou ani alternativou indexování v plné délce. Může také způsobit špatné výsledky při nevhodném použití indexu předpon. Musíte tedy určit, jaký typ dotazu a dat potřebujete načíst.

Pokrývající indexy

Pokrytí indexů nevyžaduje v MySQL žádnou speciální syntaxi. Krycí index v InnoDB se týká případu, kdy jsou všechna pole vybraná v dotazu pokryta indexem. K přečtení dat v tabulce není nutné provádět sekvenční čtení přes disk, ale pouze používat data v indexu, což výrazně urychluje dotaz. Například náš dřívější dotaz, tj. 

select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G

Jak již bylo zmíněno, je to krycí index. Když máte po ukládání dat a správně vytvořeném indexu velmi dobře naplánované tabulky, snažte se, aby vaše dotazy byly navrženy tak, aby využívaly krycí index, abyste z toho měli prospěch. To vám může pomoci maximalizovat efektivitu vašich dotazů a dosáhnout skvělého výkonu.

Využijte nástroje, které nabízejí poradce nebo sledování výkonu dotazů

Organizace mají zpočátku tendenci dávat přednost githubu a najít software s otevřeným zdrojovým kódem, který může nabídnout skvělé výhody. Pro jednoduché rady, které vám pomohou optimalizovat vaše dotazy, můžete využít Percona Toolkit. Pro MySQL DBA je Percona Toolkit jako švýcarský armádní nůž.

Pro operace musíte analyzovat, jak používáte své indexy, můžete použít pt-index-usage.

K dispozici je také Pt-query-digest, který dokáže analyzovat dotazy MySQL z protokolů, seznamu procesů a tcpdump. Ve skutečnosti je nejdůležitějším nástrojem, který musíte pro analýzu a kontrolu špatných dotazů použít, pt-query-digest. Pomocí tohoto nástroje můžete shromáždit podobné dotazy a vytvořit přehled o těch, které zabírají nejvíce času na provedení.

Pro archivaci starých záznamů můžete použít pt-archiver. Při kontrole duplicitních indexů ve vaší databázi využijte pt-duplicate-key-checker. Můžete také využít výhody pt-deadlock-logger. Přestože zablokování není příčinou nevýkonného a neefektivního dotazu, ale špatné implementace, má dopad na neefektivnost dotazu. Pokud potřebujete údržbu tabulky a vyžadujete, abyste přidávali indexy online, aniž by to ovlivnilo databázový provoz směřující do konkrétní tabulky, můžete použít pt-online-schema-change. Případně můžete použít gh-ost, který je také velmi užitečný pro migraci schémat.

Pokud hledáte podnikové funkce spojené se spoustou funkcí od výkonu dotazů a monitorování, alarmů a výstrah, řídicích panelů nebo metrik, které vám pomohou optimalizovat vaše dotazy, a poradců, ClusterControl může být nástrojem pro vy. ClusterControl nabízí mnoho funkcí, které vám zobrazí nejčastější dotazy, spuštěné dotazy a odlehlé hodnoty dotazů. Podívejte se na tento blog MySQL Query Performance Tuning, který vás provede tím, jak být na stejné úrovni pro monitorování vašich dotazů pomocí ClusterControl.

Závěr

Jak jste se dostali k závěrečné části našeho blogu dvou řad. Popsali jsme zde faktory, které způsobují degradaci dotazů, a jak je vyřešit, abyste maximalizovali databázové dotazy. Sdíleli jsme také některé nástroje, které vám mohou pomoci a pomohou vyřešit vaše problémy.


  1. Jaký je rozdíl mezi Scope_Identity(), Identity(), @@Identity a Ident_Current()?

  2. Jak filtrovat řádky bez NULL ve sloupci

  3. Rozdíl mezi LIKE a =v MYSQL?

  4. The Adaptive Join Threshold