Введение
MySQL предоставляет команду EXPLAIN, которая можетSELECTВыписка анализируется и выводитсяSELECTДетали выполнения для целевой оптимизации разработчиками.
Использовать команду EXPLAIN очень просто, просто добавьте объяснение перед оператором SELECT, например:
EXPLAIN SELECT * from user_info WHERE id < 300;
Подготовить
Чтобы облегчить демонстрацию использования EXPLAIN, сначала нам нужно создать две тестовые таблицы и добавить соответствующие данные:
CREATE TABLE `user_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '',
`age` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_index` (`name`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
INSERT INTO user_info (name, age) VALUES ('xys', 20);
INSERT INTO user_info (name, age) VALUES ('a', 21);
INSERT INTO user_info (name, age) VALUES ('b', 23);
INSERT INTO user_info (name, age) VALUES ('c', 50);
INSERT INTO user_info (name, age) VALUES ('d', 15);
INSERT INTO user_info (name, age) VALUES ('e', 20);
INSERT INTO user_info (name, age) VALUES ('f', 21);
INSERT INTO user_info (name, age) VALUES ('g', 23);
INSERT INTO user_info (name, age) VALUES ('h', 50);
INSERT INTO user_info (name, age) VALUES ('i', 15);
CREATE TABLE `order_info` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`user_id` BIGINT(20) DEFAULT NULL,
`product_name` VARCHAR(50) NOT NULL DEFAULT '',
`productor` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p2', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (1, 'p1', 'DX');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (2, 'p5', 'WL');
INSERT INTO order_info (user_id, product_name, productor) VALUES (3, 'p3', 'MA');
INSERT INTO order_info (user_id, product_name, productor) VALUES (4, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (6, 'p1', 'WHH');
INSERT INTO order_info (user_id, product_name, productor) VALUES (9, 'p8', 'TE');
EXPLAIN выходной формат
Вывод команды EXPLAIN примерно следующий:
mysql> explain select * from user_info where id = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
Значение каждого столбца следующее:
-
id: идентификатор запроса SELECT. Каждому SELECT автоматически присваивается уникальный идентификатор.
-
select_type: тип запроса SELECT.
-
таблица: какая таблица запрашивается
-
разделы: соответствующие разделы
-
тип: тип соединения
-
возможных_ключей: индексы, которые можно использовать в этом запросе.
-
ключ: точный индекс, используемый в этом запросе.
-
ref: какое поле или константу использовать с ключом
-
rows: показывает, сколько строк было просмотрено этим запросом.Это оценка.
-
filtered: указывает процент данных, отфильтрованных по этому условию запроса.
-
дополнительно: дополнительная информация
Далее, давайте сосредоточимся на некоторых из наиболее важных полей.
select_type
select_typeУказывает тип запроса, и его общие значения:
-
SIMPLE означает, что этот запрос не содержит запроса UNION или подзапроса.
-
PRIMARY, указывающий, что этот запрос является самым внешним запросом
-
UNION, указывающий, что этот запрос является вторым или последующим запросом к UNION.
-
ЗАВИСИМОЕ ОБЪЕДИНЕНИЕ, второй или последующий оператор запроса в ОБЪЕДИНЕНИИ, в зависимости от внешнего запроса
-
UNION RESULT, результат UNION
-
ПОДЗАПРОС, первый SELECT в подзапросе
-
ЗАВИСИМЫЙ ПОДЗАПРОС: первый SELECT в подзапросе, который зависит от внешнего запроса, то есть подзапрос зависит от результата внешнего запроса.
Наиболее распространенная категория запросов должна бытьSIMPLEНапример, если в нашем запросе нет ни подзапросов, ни запросов UNION, то обычноSIMPLEтипа, например:
mysql> explain select * from user_info where id = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
Если бы мы использовали запрос UNION, вывод EXPLAIN выглядел бы так:
mysql> EXPLAIN (SELECT * FROM user_info WHERE id IN (1, 2, 3))
-> UNION
-> (SELECT * FROM user_info WHERE id IN (3, 4, 5));
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
| 1 | PRIMARY | user_info | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where |
| 2 | UNION | user_info | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where |
| NULL | UNION RESULT | <union1,2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary |
+----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+
3 rows in set, 1 warning (0.00 sec)
table
Указывает таблицу или производную таблицу, задействованную в запросе.
type
typeПоле более важно, оно обеспечивает важную основу для оценки эффективности запроса.typeполе, мы считаем, что этот запрос全表扫描все еще索引扫描Ждать.
тип общий тип
Обычно используемые значения для типа:
-
system: В таблице только одни данные.Этот тип особенныйconstтип. -
const: Эквивалентное сканирование запроса по первичному ключу или уникальному индексу возвращает не более одной строки данных.Константный запрос очень быстрый, потому что его нужно прочитать только один раз.
Например, в приведенном ниже запросе используется индекс первичного ключа, поэтомуtypeэтоconstТип.
mysql> explain select * from user_info where id = 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
1 row in set, 1 warning (0.00 sec)
-
eq_ref: этот тип обычно появляется в запросах на соединение нескольких таблиц, указывая, что для каждого результата предыдущей таблицы может быть сопоставлена только одна строка результатов из последней таблицы.И операция сравнения запроса обычно=, эффективность запросов выше, например:
mysql> EXPLAIN SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: index
possible_keys: user_product_detail_index
key: user_product_detail_index
key_len: 314
ref: NULL
rows: 9
filtered: 100.00
Extra: Using where; Using index
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: test.order_info.user_id
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)
-
ref: этот тип обычно появляется в запросах на соединение нескольких таблиц, нацеленных на индексы с неуникальными или не первичными ключами или использующих最左前缀Запрос индекса правил.
Например, в следующем примере используетсяrefТип запроса:
mysql> EXPLAIN SELECT * FROM user_info, order_info WHERE user_info.id = order_info.user_id AND order_info.user_id = 5\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: const
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: const
rows: 1
filtered: 100.00
Extra: NULL
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: ref
possible_keys: user_product_detail_index
key: user_product_detail_index
key_len: 9
ref: const
rows: 1
filtered: 100.00
Extra: Using index
2 rows in set, 1 warning (0.01 sec)
-
range: указывает, что используется запрос диапазона индекса, и некоторые записи данных в таблице получены через диапазон поля индекса.Этот тип обычно появляется в =, , >, >=, , МЕЖДУ, В() в работе.
когдаtypeдаrange, затем EXPLAIN выводитrefполе равно NULL иkey_lenЭто поле с самым длинным индексом, используемым в этом запросе.
Например, следующий пример представляет собой запрос диапазона:
mysql> EXPLAIN SELECT *
-> FROM user_info
-> WHERE id BETWEEN 2 AND 8 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: NULL
rows: 7
filtered: 100.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
-
index: представляет полное сканирование индекса, аналогичное типу ALL, за исключением того, что тип ALL представляет собой полное сканирование таблицы, а тип индекса сканирует только все индексы без сканирования данных.indexТип обычно возникает, когда: запрашиваемые данные могут быть получены непосредственно в дереве индекса без сканирования данных В этом случае в поле Дополнительно будет отображатьсяUsing index.
Например:
mysql> EXPLAIN SELECT name FROM user_info \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: index
possible_keys: NULL
key: name_index
key_len: 152
ref: NULL
rows: 10
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
В приведенном выше примере поле имени, которое мы запрашиваем, является индексом, поэтому мы можем получить данные непосредственно из индекса, чтобы удовлетворить требования запроса, не запрашивая данные в таблице.Таким образом, в этом случае значение типа равноindex, а значение Extra равноUsing index.
-
ALL: указывает на полное сканирование таблицы. Этот тип запроса является одним из наименее эффективных запросов. Вообще говоря, наш запрос не должен иметь тип запроса ALL, потому что такой запрос содержит большой объем данных. Производительность — это огромная катастрофа. . Если запрос является запросом типа ALL, его обычно можно избежать, добавив индекс к соответствующему полю. Ниже приведен пример полного сканирования таблицы. Вы можете видеть, что во время полного сканирования таблицы, возможные_ключи и ключ Поля все имеют значение NULL, указывая на то, что индекс не используется, а строки очень велики, поэтому эффективность всего запроса очень низкая.
mysql> EXPLAIN SELECT age FROM user_info WHERE age = 20 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user_info
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10
filtered: 10.00
Extra: Using where
1 row in set, 1 warning (0.00 sec)
Сравнение производительности типов
Вообще говоря, отношения производительности разных типов выглядят следующим образом:ALL < index < range ~ index_merge < ref < eq_ref < const < systemALLПоскольку этот тип представляет собой полное сканирование таблицы, он является самым медленным при тех же условиях запроса.
иindexЭтот тип запроса не является полным сканированием таблицы, но он сканирует все индексы и поэтому немного быстрее, чем тип ALL.
Последние типы используют индексы для запроса данных,
Следовательно, часть или большую часть данных можно отфильтровать, поэтому эффективность запросов относительно высока.
possible_keys
possible_keysУказывает индексы, которые MySQL может использовать при выполнении запросов. Обратите внимание, что даже некоторые индексыpossible_keysпоявляется в , но это не означает, что этот индекс будет фактически использоваться MySQL.Какие индексы используются MySQL при запросе, определяетсяkeyполевое решение.
key
Это поле является индексом, фактически используемым MySQL в текущем запросе.
key_len
Указывает количество байтов индекса, используемого оптимизатором запросов. Это поле может оценить, используется ли составной индекс полностью или используется только крайнее левое поле. Правила расчета для .key_len следующие:
-
нить
-
char(n): длина n байтов
-
varchar(n): 3, если кодировка utf8n + 2 байта; 4, если кодировка utf8mb4n + 2 байта.
-
-
Числовой тип:
-
TINYINT: 1 байт
-
МАЛЕНЬКИЙ INT: 2 байта
-
СРЕДНИЙ: 3 байта
-
INT: 4 байта
-
БОЛЬШОЙ: 8 байт
-
-
тип времени
-
ДАТА: 3 байта
-
TIMESTAMP: 4 байта
-
ДАТАВРЕМЯ: 8 байт
-
-
Атрибуты поля: Атрибут NULL занимает один байт.Если поле НЕ NULL, такого атрибута нет.
Возьмем два простых каштана:
mysql> EXPLAIN SELECT * FROM order_info WHERE user_id < 3 AND product_name = 'p1' AND productor = 'WHH' \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: range
possible_keys: user_product_detail_index
key: user_product_detail_index
key_len: 9
ref: NULL
rows: 5
filtered: 11.11
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
В приведенном выше примере запрашивается указанное содержимое из таблицы order_info, и из оператора создания таблицы этой таблицы мы можем узнать, что таблицаorder_infoСуществует федеративный индекс:
KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
Однако этот запросWHERE user_id < 3 AND product_name = 'p1' AND productor = 'WHH', так как запрос диапазона user_id выполняется первым и в соответствии с最左前缀匹配В принципе, когда встречается запрос диапазона, сопоставление индекса останавливается, поэтому фактически поля индекса, которые мы используем, являются толькоuser_id, Таким образом, вEXPLAIN, отображаемое значение key_len равно 9. Поскольку
Поле user_id имеет значение BIGINT и занимает 8 байт, а атрибут NULL занимает 1 байт, то есть всего 9 байт.Если мы изменим поле user_id наBIGINT(20) NOT NULL DEFAULT '0', то key_length должно быть равно 8.
выше, потому что最左前缀匹配принцип, наш запрос использует только совместный индексuser_idполе, так что эффективность не высока.
Давайте посмотрим на следующий пример:
mysql> EXPLAIN SELECT * FROM order_info WHERE user_id = 1 AND product_name = 'p1' \G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: ref
possible_keys: user_product_detail_index
key: user_product_detail_index
key_len: 161
ref: const,const
rows: 2
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
В этом запросе мы не использовали диапазонный запрос, а значение key_len равно 161. Почему? Потому что условия нашего запросаWHERE user_id = 1 AND product_name = 'p1', используются только первые два поля в объединенном индексе, поэтомуkeyLen(user_id) + keyLen(product_name) = 9 + 50 * 3 + 2 = 161
rows
Строки также являются важным полем. Оптимизатор запросов MySQL оценивает количество строк данных, которые SQL должен просмотреть и прочитать, чтобы найти результирующий набор на основе статистической информации. Это значение является очень интуитивным индикатором эффективности SQL. В принципе , чем меньше строк, тем лучше.
Extra
Много дополнительной информации в EXplain будет отображаться в поле «Дополнительно», общие из них следующие:
-
Using filesort
когда Экстра имеетUsing filesort, это означает, что MySQL нуждается в дополнительных операциях сортировки, и эффект сортировки не может быть достигнут за счет порядка индекса.Using filesort, рекомендуется его оптимизировать и удалить, так как такой запрос потребляет много ресурсов ЦП.
Например следующий пример:
mysql> EXPLAIN SELECT * FROM order_info ORDER BY product_name \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: index
possible_keys: NULL
key: user_product_detail_index
key_len: 253
ref: NULL
rows: 9
filtered: 100.00
Extra: Using index; Using filesort
1 row in set, 1 warning (0.00 sec)
Наш индекс
KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`)
Но в приведенном выше запросе в соответствии сproduct_nameдля сортировки и, следовательно, не может быть оптимизирован с помощью индекса, который, в свою очередь, производитUsing filesort.
Если мы изменим сортировку наORDER BY user_id, product_name, то не появитсяUsing filesort. Например:
mysql> EXPLAIN SELECT * FROM order_info ORDER BY user_id, product_name \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: order_info
partitions: NULL
type: index
possible_keys: NULL
key: user_product_detail_index
key_len: 253
ref: NULL
rows: 9
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
-
Использование индекса «покрывающее сканирование индекса» означает, что запрос может найти необходимые данные в дереве индекса без сканирования файла данных таблицы, что часто означает хорошую производительность.
-
При использовании временного запроса используется временная таблица, что обычно происходит при сортировке, группировке и объединении нескольких таблиц.Эффективность запроса невысока, поэтому рекомендуется оптимизация.