您现在的位置是:网站首页> 编程资料编程资料

一步步教你MySQL查询优化分析教程_Mysql_

2023-05-27 343人已围观

简介 一步步教你MySQL查询优化分析教程_Mysql_

前言

MySQL是关系性数据库中的一种,查询功能强,数据一致性高,数据安全性高,支持二级索引。但性能方面稍逊于非关系性数据库,特别是百万级别以上的数据,很容易出现查询慢的现象。这时候需要分析查询慢的原因,一般情况下是程序员sql写的烂,或者是没有键索引,或者是索引失效等原因导致的。

这时候MySQL 提供的 EXPLAIN 命令就尤其重要, 它可以对 SELECT 语句进行分析, 并输出 SELECT 执行的详细信息, 以供开发人员针对性优化.

而且就在查询语句前加上 Explain 就成:

 EXPLAIN SELECT * FROM customer WHERE id < 100;

准备

首先需要建立两个测试用表及数据:

 CREATE TABLE `customer` ( `id` BIGINT(20) unsigned NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL DEFAULT '', `age` INT(11) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `name_index` (`name`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 INSERT INTO customer (name, age) VALUES ('a', 1); INSERT INTO customer (name, age) VALUES ('b', 2); INSERT INTO customer (name, age) VALUES ('c', 3); INSERT INTO customer (name, age) VALUES ('d', 4); INSERT INTO customer (name, age) VALUES ('e', 5); INSERT INTO customer (name, age) VALUES ('f', 6); INSERT INTO customer (name, age) VALUES ('g', 7); INSERT INTO customer (name, age) VALUES ('h', 8); INSERT INTO customer (name, age) VALUES ('i', 9);
 CREATE TABLE `orders` ( `id` BIGINT(20) unsigned NOT NULL AUTO_INCREMENT, `user_id` BIGINT(20) unsigned NOT NULL DEFAULT 0, `product_name` VARCHAR(50) NOT NULL DEFAULT '', `productor` VARCHAR(30) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `user_product_detail_index` (`user_id`, `product_name`, `productor`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 INSERT INTO orders (user_id, product_name, productor) VALUES (1, 'p1', 'WHH'); INSERT INTO orders (user_id, product_name, productor) VALUES (1, 'p2', 'WL'); INSERT INTO orders (user_id, product_name, productor) VALUES (1, 'p1', 'DX'); INSERT INTO orders (user_id, product_name, productor) VALUES (2, 'p1', 'WHH'); INSERT INTO orders (user_id, product_name, productor) VALUES (2, 'p5', 'WL'); INSERT INTO orders (user_id, product_name, productor) VALUES (3, 'p3', 'MA'); INSERT INTO orders (user_id, product_name, productor) VALUES (4, 'p1', 'WHH'); INSERT INTO orders (user_id, product_name, productor) VALUES (6, 'p1', 'WHH'); INSERT INTO orders (user_id, product_name, productor) VALUES (9, 'p8', 'TE');

EXPLAIN 输出格式

EXPLAIN 命令的输出内容大致如下:

 mysql> explain select * from customer where id = 1\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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 查询的类型.
  • table: 查询的是哪个表partitions: 匹配的分区type: join 类型
  • possible_keys: 此次查询中可能选用的索引
  • key: 此次查询中确切使用到的索引.
  • ref: 哪个字段或常数与 key 一起被使用
  • rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
  • filtered: 表示此查询条件所过滤的数据的百分比
  • extra: 额外的信息

接下来我们来重点看一下比较重要的几个字段.

select_type

  • SIMPLE —— 简单的select 查询,查询中不包含子查询或者UNION
  • PRIMARY —— 查询中若包含任何复杂的子查询,最外层查询则被标记为primary
  • UNION —— 表示此查询是 UNION 的第二或随后的查询
  • DEPENDENT UNION —— UNION 中的第二个或后面的查询语句, 取决于外面的查询
  • UNION RESULT —— 从UNION表获取结果的select结果
  • DERIVED —— 在from列表中包含的子查询被标记为derived(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
  • SUBQUERY —— 在select或where 列表中包含了子查询
  • DEPENDENT SUBQUERY —— 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.

最常见的查询类别应该是 SIMPLE 了, 比如当我们的查询没有子查询, 也没有 UNION 查询时, 那么通常就是 SIMPLE 类型, 例如:

 mysql> explain select * from customer where id = 2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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 customer WHERE id IN (1, 2, 3)) -> UNION -> (SELECT * FROM customer WHERE id IN (3, 4, 5)); +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+ | 1 | PRIMARY | customer | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where | | 2 | UNION | customer | NULL | range | PRIMARY | PRIMARY | 8 | NULL | 3 | 100.00 | Using where | | NULL | UNION RESULT |  | NULL | ALL | NULL | NULL | NULL | NULL | NULL | NULL | Using temporary | +----+--------------+------------+------------+-------+---------------+---------+---------+------+------+----------+-----------------+ 3 rows in set, 1 warning (0.00 sec)

table

表示查询涉及的表或衍生表

type

type 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等.

type 常用类型

type 常用的取值有:

  • system: 表中只有一条数据. 这个类型是特殊的 const 类型.
  • const: 针对主键或唯一索引的等值查询扫描, 最多只返回一行数据. const 查询速度非常快, 因为它仅仅读取一次即可.例如下面的这个查询, 它使用了主键索引, 因此 type 就是 const 类型的.
 mysql> explain select * from customer where id = 2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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: 此类型通常出现在多表的 join 查询, 表示对于前表的每一个结果, 都只能匹配到后表的一行结果. 并且查询的比较操作通常是 =, 查询效率较高. 例如:

 mysql> EXPLAIN SELECT * FROM customer, order_info WHERE customer.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: customer 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: 此类型通常出现在多表的 join 查询, 针对于非唯一或非主键索引, 或者是使用了 最左前缀 规则索引的查询.

例如下面这个例子中, 就使用到了 ref 类型的查询:

 mysql> EXPLAIN SELECT * FROM customer, order_info WHERE customer.id = order_info.user_id AND order_info.user_id = 5\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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: 表示使用索引范围查询, 通过索引字段范围获取表中部分数据记录. 这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中.当 type 是 range 时, 那么 EXPLAIN 输出的 ref 字段为 NULL, 并且 key_len 字段是此次查询中使用到的索引的最长的那个.

例如下面的例子就是一个范围查询:

 mysql> EXPLAIN SELECT * FROM customer WHERE id BETWEEN 2 AND 8 \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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: 表示全索引扫描(full index scan), 和 ALL 类型类似, 只不过 ALL 类型是全表扫描, 而 index 类型则仅仅扫描所有的索引, 而不扫描数据.

index 类型通常出现在: 所要查询的数据直接在索引树中就可以获取到, 而不需要扫描数据. 当是这种情况时, Extra 字段 会显示 Using index.

例如:

 mysql> EXPLAIN SELECT name FROM customer \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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)

上面的例子中, 我们查询的 name 字段恰好是一个索引, 因此我们直接从索引中获取数据就可以满足查询的需求了, 而不需要查询表中的数据. 因此这样的情况下, type 的值是 index, 并且 Extra 的值是 Using index.

  • ALL: 表示全表扫描, 这个类型的查询是性能最差的查询之一. 通常来说, 我们的查询不应该出现 ALL 类型的查询, 因为这样的查询在数据量大的情况下, 对数据库的性能是巨大的灾难. 如一个查询是 ALL 类型查询, 那么一般来说可以对相应的字段添加索引来避免.

下面是一个全表扫描的例子, 可以看到, 在全表扫描时, possible_keys 和 key 字段都是 NULL, 表示没有使用到索引, 并且 rows 十分巨大, 因此整个查询效率是十分低下的.

 mysql> EXPLAIN SELECT age FROM customer WHERE age = 20 \G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: customer 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)

type 类型的性能比较

通常来说, 不同的 type 类型的性能关系如下:

ALL < index < range ~ index_merge < ref < eq_ref < const < system

ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的.

而 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了.

对程序员来说,若保证查询至少达到range级别或者最好能达到ref则算是一个优秀而又负责的程序员。

-六神源码网