MariaDB
 sql >> база данни >  >> RDS >> MariaDB

Увеличаване на ефективността на заявки към база данни за MySQL - част втора

Това е втората част от серия от две части блог за максимизиране на ефективността на заявките за база данни в MySQL. Можете да прочетете първа част тук.

Използване на единична колона, съставен, префикс и индекс на покриване

Таблиците, които често получават голям трафик, трябва да бъдат правилно индексирани. Важно е не само да индексирате вашата таблица, но също така трябва да определите и анализирате какви са типовете заявки или видовете извличане, които са ви необходими за конкретната таблица. Силно се препоръчва да анализирате какъв тип заявки или извличане на данни са ви необходими за конкретна таблица, преди да решите какви индекси са необходими за таблицата. Нека разгледаме тези типове индекси и как можете да ги използвате, за да увеличите максимално ефективността на заявката си.

Индекс с една колона

InnoD таблицата може да съдържа максимум 64 вторични индекса. Индекс с една колона (или индекс с цяла колона) е индекс, присвоен само на определена колона. Създаването на индекс към конкретна колона, която съдържа различни стойности, е добър кандидат. Добрият индекс трябва да има висока мощност и статистика, така че оптимизаторът да може да избере правилния план за заявка. За да видите разпределението на индексите, можете да проверите със синтаксис SHOW INDEXES точно както по-долу:

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)

Можете да проверите и с таблици information_schema.index_statistics или mysql.innodb_index_stats.

Сложни (композитни) или многокомпонентни индекси

Съставният индекс (обикновено наричан съставен индекс) е индекс от няколко части, съставен от множество колони. MySQL позволява до 16 колони, ограничени за конкретен съставен индекс. Превишаването на лимита връща грешка като по-долу:

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

Съставният индекс осигурява тласък на вашите заявки, но изисква да имате ясно разбиране за това как извличате данните. Например таблица с 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

...което се състои от съставен индекс `name`. Съставният индекс подобрява производителността на заявката, след като тези ключове са препратки като използвани ключови части. Например, вижте следното:

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

Used_key_parts показват, че планът на заявката е избрал перфектно желаните от нас колони, обхванати в нашия съставен индекс.

Съставното индексиране също има своите ограничения. Определени условия в заявката не могат да приемат всички колони като част от ключа.

В документацията се казва, "Оптимизаторът се опитва да използва допълнителни ключови части, за да определи интервала, стига операторът за сравнение да е =, <=> или IS NULL. Ако операторът е> , <,>=, <=, !=, <>, BETWEEN или LIKE, оптимизаторът го използва, но не разглежда повече ключови части. За следния израз оптимизаторът използва =от първото сравнение. Той също така използва>=от второто сравнение, но не взема предвид други ключови части и не използва третото сравнение за изграждане на интервали..." . По принцип това означава, че независимо че имате съставен индекс за две колони, примерна заявка по-долу не покрива и двете полета:

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)

В този случай (и ако вашата заявка е повече от диапазони вместо константни или референтни типове) избягвайте използването на съставни индекси. Това просто губи паметта и буфера ви и увеличава влошаването на производителността на вашите заявки.

Префиксни индекси

Префиксните индекси са индекси, които съдържат колони, посочени като индекс, но приемат само началната дължина, дефинирана за тази колона, и тази част (или данни за префикс) са единствената част, съхранявана в буфера. Индексите на префиксите могат да помогнат за намаляване на ресурсите на буферния пул, а също и на дисковото ви пространство, тъй като не е необходимо да поема цялата дължина на колоната. Какво означава това? Да вземем пример. Нека сравним въздействието между индекса в цяла дължина спрямо индекса на префикса.

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

Създадохме съставен индекс с пълна дължина, който консумира общо 36MiB таблично пространство за таблицата users_account. Нека го пуснем и след това добавим индекс на префикс.

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

Използвайки индекса на префикса, той задържа само до 28MiB и това е по-малко от 8MiB от използването на индекс с пълна дължина. Това е страхотно да се чуе, но това не означава, че е производително и обслужва това, от което се нуждаете.

Ако решите да добавите префиксен индекс, първо трябва да определите какъв тип заявка за извличане на данни ви е необходима. Създаването на префиксен индекс ви помага да използвате по-голяма ефективност с буферния пул и така помага за ефективността на заявката ви, но също така трябва да знаете неговото ограничение. Например, нека сравним производителността при използване на индекс с пълна дължина и индекс на префикс.

Нека създадем индекс с пълна дължина с помощта на съставен индекс,

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)

Резултатът разкрива, че всъщност използва покриващ индекс, т.е. "using_index":true и използва индексите правилно, т.е. Handler_read_key се увеличава и прави индексно сканиране, докато Handler_read_next се увеличава.

Сега нека опитаме да използваме префиксен индекс на същия подход,

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 разкрива, че използва индекс правилно, но е забележимо, че има режийни разходи в сравнение с индекс с пълна дължина. Това е очевидно и обяснимо, тъй като индексът на префикса не покрива цялата дължина на стойностите на полето. Използването на префиксен индекс не е заместител, нито алтернатива на индексирането в цяла дължина. Освен това може да създаде лоши резултати при неправилно използване на индекса на префикса. Така че трябва да определите какъв тип заявка и данни трябва да извлечете.

Покриващи индекси

Покриването на индекси не изисква специален синтаксис в MySQL. Покриващ индекс в InnoDB се отнася до случая, когато всички избрани полета в заявка са покрити от индекс. Не е необходимо да се извършва последователно четене на диска, за да се прочетат данните в таблицата, а се използват само данните в индекса, което значително ускорява заявката. Например, нашата заявка по-рано, т.е. 

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

Както споменахме по-рано, това е покриващ индекс. Когато имате много добре планирани таблици при съхраняване на вашите данни и правилно създаден индекс, опитайте се да направите възможно вашите заявки да са проектирани да използват покриващия индекс, така че да се възползвате от резултата. Това може да ви помогне да увеличите максимално ефективността на вашите заявки и да доведе до страхотна производителност.

Използвайте инструменти, които предлагат съветници или мониторинг на ефективността на заявките

Организациите често първоначално са склонни първо да използват github и да намерят софтуер с отворен код, който може да предложи големи предимства. За прости съвети, които ви помагат да оптимизирате вашите заявки, можете да използвате Percona Toolkit. За MySQL DBA, Percona Toolkit е като швейцарско армейско ножче.

За операции трябва да анализирате как използвате вашите индекси, можете да използвате pt-index-usage.

Pt-query-digest също е наличен и може да анализира MySQL заявки от регистрационни файлове, списък с процеси и tcpdump. Всъщност най-важният инструмент, който трябва да използвате за анализиране и проверка на лоши заявки, е pt-query-digest. Използвайте този инструмент, за да обединявате подобни заявки и да докладвате за тези, които отнемат най-много време за изпълнение.

За архивиране на стари записи можете да използвате pt-archiver. Проверявайки вашата база данни за дублиращи се индекси, използвайте pt-duplicate-key-checker. Можете също да се възползвате от pt-deadlock-logger. Въпреки че блокирането не е причина за слаба и неефективна заявка, а за лоша реализация, все пак оказва влияние върху неефективността на заявката. Ако имате нужда от поддръжка на таблица и изисква да добавяте индекси онлайн, без да засягате трафика на базата данни, отиващ към определена таблица, тогава можете да използвате pt-online-schema-change. Като алтернатива можете да използвате gh-ost, който също е много полезен за миграции на схеми.

Ако търсите функции за предприятие, в комплект с много функции от производителност и наблюдение на заявки, аларми и сигнали, табла за управление или показатели, които ви помагат да оптимизирате заявките си, и съветници, ClusterControl може да е инструментът за Вие. ClusterControl предлага много функции, които ви показват водещи заявки, изпълнявани заявки и отклонения от заявки. Разгледайте този блог MySQL Query Performance Tuning, който ви напътства как да сте на ниво за наблюдение на заявките си с ClusterControl.

Заключение

Тъй като стигнахте до финалната част на нашия блог от две серии. Тук разгледахме факторите, които причиняват влошаване на заявката и как да го разрешим, за да увеличите максимално вашите заявки за база данни. Споделихме и някои инструменти, които могат да ви бъдат от полза и да помогнат за решаването на вашите проблеми.


  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 VERSION() Обяснено

  2. Как работи RLIKE в MariaDB

  3. Отключване на предимствата на програмата за сертифициран сътрудник на MariaDB

  4. Как SIGN() работи в MariaDB

  5. Как работи UPPER() в MariaDB