MariaDB
 sql >> Baza danych >  >> RDS >> MariaDB

Maksymalizacja wydajności zapytań do bazy danych dla MySQL — część druga

To druga część dwuczęściowego bloga z serii poświęconej maksymalizacji wydajności zapytań do bazy danych w MySQL. Część pierwszą możesz przeczytać tutaj.

Korzystanie z indeksu jednokolumnowego, złożonego, prefiksu i indeksu obejmującego

Tabele, które często otrzymują duży ruch, muszą być odpowiednio zaindeksowane. Ważne jest nie tylko zindeksowanie tabeli, ale także określenie i przeanalizowanie typów zapytań lub typów pobierania, które są potrzebne dla określonej tabeli. Zdecydowanie zaleca się, aby przed podjęciem decyzji, jakie indeksy są wymagane dla tabeli, przeanalizować, jakiego typu zapytania lub pobieranie danych potrzebujesz w określonej tabeli. Przyjrzyjmy się tym typom indeksów i sposobom ich wykorzystania, aby zmaksymalizować wydajność zapytań.

Indeks jednokolumnowy

Tabela InnoD może zawierać maksymalnie 64 indeksy pomocnicze. Indeks jednokolumnowy (lub indeks pełnokolumnowy) to indeks przypisany tylko do określonej kolumny. Dobrym kandydatem jest utworzenie indeksu do określonej kolumny, która zawiera różne wartości. Dobry indeks musi mieć wysoką kardynalność i statystyki, aby optymalizator mógł wybrać odpowiedni plan zapytania. Aby zobaczyć rozkład indeksów, możesz sprawdzić składnię POKAŻ INDEKSY, tak jak poniżej:

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)

Możesz również sprawdzać za pomocą tabel information_schema.index_statistics lub mysql.innodb_index_stats.

Indeksy złożone (złożone) lub wieloczęściowe

Indeks złożony (powszechnie nazywany indeksem złożonym) to wieloczęściowy indeks złożony z wielu kolumn. MySQL umożliwia do 16 kolumn ograniczonych dla określonego indeksu złożonego. Przekroczenie limitu zwraca błąd jak poniżej:

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

Złożony indeks zapewnia przyspieszenie zapytań, ale wymaga dokładnego zrozumienia sposobu pobierania danych. Na przykład tabela z 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

...który składa się ze złożonego indeksu `name`. Indeks złożony poprawia wydajność zapytań, gdy te klucze są odwołane jako używane części kluczowe. Na przykład zobacz:

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

Parametry used_key_parts pokazują, że plan zapytania idealnie dobrał żądane kolumny uwzględnione w naszym indeksie złożonym.

Indeksowanie złożone ma również swoje ograniczenia. Niektóre warunki w zapytaniu nie mogą obejmować wszystkich kolumn części klucza.

Dokumentacja mówi, "Optymalizator próbuje użyć dodatkowych części kluczowych do określenia interwału, o ile operatorem porównania jest =, <=> lub JEST NULL. Jeśli operatorem jest> , <,>=, <=, !=, <>, BETWEEN lub LIKE, optymalizator używa go, ale nie uwzględnia więcej kluczowych części. Dla poniższego wyrażenia optymalizator używa =z pierwszego porównania. Używa również>=z drugiego porównania, ale nie uwzględnia dalszych kluczowych części i nie używa trzeciego porównania do konstrukcji przedziałów…” . Zasadniczo oznacza to, że niezależnie od tego, czy masz złożony indeks dla dwóch kolumn, przykładowe zapytanie poniżej nie obejmuje obu pól:

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)

W tym przypadku (i jeśli zapytanie obejmuje więcej zakresów zamiast typów stałych lub referencyjnych) unikaj używania indeksów złożonych. Po prostu marnuje twoją pamięć i bufor oraz zwiększa spadek wydajności twoich zapytań.

Indeksy przedrostków

Indeksy prefiksowe to indeksy, które zawierają kolumny, do których odwołuje się indeks, ale przyjmują tylko długość początkową zdefiniowaną dla tej kolumny, a ta część (lub dane prefiksu) są jedyną częścią przechowywaną w buforze. Indeksy prefiksów mogą pomóc zmniejszyć zasoby puli buforów, a także miejsce na dysku, ponieważ nie muszą zajmować całej długości kolumny.Co to oznacza? Weźmy przykład. Porównajmy wpływ między indeksem pełnej długości a indeksem prefiksu.

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

Stworzyliśmy indeks złożony o pełnej długości, który zajmuje łącznie 36 MB przestrzeni tabel dla tabeli users_account. Odrzućmy to, a następnie dodajmy indeks prefiksu.

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

Korzystając z indeksu prefiksu, mieści on tylko do 28 MB, a to mniej niż 8 MB niż przy użyciu indeksu o pełnej długości. Fajnie to słyszeć, ale nie oznacza to, że jest wydajny i służy temu, czego potrzebujesz.

Jeśli zdecydujesz się dodać indeks prefiksu, musisz najpierw określić, jakiego typu zapytania do pobierania danych potrzebujesz. Utworzenie indeksu prefiksu pomaga wykorzystać większą wydajność puli buforów, a więc pomaga w wydajności zapytania, ale trzeba również znać jego ograniczenia. Na przykład porównajmy wydajność przy użyciu indeksu pełnej długości i indeksu prefiksu.

Utwórzmy indeks o pełnej długości za pomocą indeksu złożonego,

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)

Wynik pokazuje, że w rzeczywistości używa indeksu pokrywającego, tj. „using_index”:true i używa indeksów poprawnie, tj. Handler_read_key jest zwiększany i wykonuje skanowanie indeksu, gdy Handler_read_next jest zwiększany.

Teraz spróbujmy użyć indeksu prefiksu tego samego podejścia,

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 ujawnia, że ​​używa indeksu prawidłowo, ale zauważalnie jest to narzut kosztów w porównaniu z indeksem o pełnej długości. To oczywiste i wytłumaczalne, ponieważ indeks prefiksu nie obejmuje całej długości wartości pola. Korzystanie z indeksu prefiksu nie zastępuje ani nie zastępuje indeksowania pełnej długości. Może również powodować słabe wyniki w przypadku niewłaściwego używania indeksu prefiksu. Musisz więc określić, jakiego typu zapytania i dane chcesz pobrać.

Pokrywa indeksy

Pokrywanie indeksów nie wymaga żadnej specjalnej składni w MySQL. Indeks pokrywający w InnoDB odnosi się do przypadku, gdy wszystkie pola wybrane w zapytaniu są objęte indeksem. Aby odczytać dane w tabeli, nie musi wykonywać sekwencyjnego odczytu z dysku, a jedynie korzystać z danych w indeksie, co znacznie przyspiesza zapytanie. Na przykład nasze zapytanie wcześniej, tj.

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

Jak wspomniano wcześniej, jest indeksem pokrywającym. Jeśli masz bardzo dobrze zaplanowane tabele po prawidłowym przechowywaniu danych i stworzeniu indeksu, postaraj się, aby Twoje zapytania były zaprojektowane tak, aby wykorzystywały indeks pokrywający, tak aby uzyskać korzyści z wyniku. Pomoże to zmaksymalizować wydajność zapytań i zapewnić doskonałą wydajność.

Wykorzystywanie narzędzi oferujących doradców lub monitorowanie wydajności zapytań

Organizacje często na początku kierują się najpierw na github i znajdują oprogramowanie typu open source, które może oferować ogromne korzyści. Aby uzyskać proste porady, które pomogą Ci zoptymalizować zapytania, możesz skorzystać z pakietu Percona Toolkit. W przypadku administratora bazy danych MySQL zestaw narzędzi Percona jest jak szwajcarski scyzoryk.

W przypadku operacji musisz przeanalizować, w jaki sposób używasz indeksów, możesz użyć pt-index-usage.

Pt-query-digest jest również dostępny i może analizować zapytania MySQL z dzienników, listy procesów i tcpdump. W rzeczywistości najważniejszym narzędziem, którego musisz użyć do analizowania i sprawdzania złych zapytań, jest pt-query-digest. Użyj tego narzędzia, aby agregować podobne zapytania i raportować te, które zużywają najwięcej czasu na wykonanie.

Do archiwizacji starych rekordów można użyć pt-archiver. Sprawdzając bazę danych pod kątem zduplikowanych indeksów, skorzystaj z funkcji pt-duplicate-key-checker. Możesz także skorzystać z pt-deadlock-logger. Chociaż zakleszczenia nie są przyczyną nieefektywnego i nieefektywnego zapytania, ale słabą implementacją, wpływają na nieefektywność zapytań. Jeśli potrzebujesz konserwacji tabel i musisz dodawać indeksy online bez wpływu na ruch bazy danych przechodzący do określonej tabeli, możesz użyć pt-online-schema-change. Alternatywnie możesz użyć gh-ost, który jest również bardzo przydatny do migracji schematów.

Jeśli szukasz funkcji korporacyjnych, w pakiecie z wieloma funkcjami, od wydajności zapytań i monitorowania, alarmów i alertów, pulpitów nawigacyjnych lub metryk, które pomogą Ci zoptymalizować zapytania i doradców, ClusterControl może być narzędziem do ty. ClusterControl oferuje wiele funkcji, które pokazują najpopularniejsze zapytania, uruchomione zapytania i wartości odstające zapytań. Zajrzyj na ten blog MySQL Query Performance Tuning, który pokazuje, jak być na równi w zakresie monitorowania zapytań za pomocą ClusterControl.

Wnioski

Tak jak dotarłeś do końcowej części naszego dwuseryjnego bloga. Omówiliśmy tutaj czynniki, które powodują degradację zapytań i sposoby ich rozwiązania w celu zmaksymalizowania zapytań do bazy danych. Udostępniliśmy również kilka narzędzi, które mogą Ci przynieść korzyści i pomóc rozwiązać Twoje problemy.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. MariaDB JSON_ARRAY() Objaśnienie

  2. Jak działa funkcja COALESCE() w MariaDB

  3. Jak CHR() działa w MariaDB

  4. Korzystaj z mycli i ucz się MariaDB/MySQL wygodnie w terminalu!

  5. Uruchamianie MariaDB w konfiguracji chmury hybrydowej