5

mysql之执行计划的入门理解-m6米乐安卓版下载

原创 大表哥 2022-03-07
1219

image.png

大家好!本次继续带来mysql之执行计划的入门理解–下篇:

大表哥会带来执行计划中剩下的几个指标理解: possible_keys,key,key_len,ref,rows,filtered,extra

1)possible_keys 和 key

possible_keys: 表示索引访问表的所有可能性,这个一般出现的越多,代表可能索引的索引就越多,就有可能存在重复索引的情况。
负面影响有2点:
1)增加了dml的索引维护,降低了该表的dml的效率
2)迷惑了优化器(特别是在统计信息不准的情况下)可能会选择不好的索引作为执行计划

key: 如果是索引访问表的话,最终在possible_keys中选择的一个索引名字

我们来看一个重复索引的例子: 很明显 索引 idx_name 和 idx_name_age 是重复的关系。(索引idx_name_age 包含了 idx_name )

mysql> create table t4 (id int not null primary key, name varchar(20),age int); query ok, 0 rows affected (0.01 sec) mysql> alter table t4 add index idx_name (name); query ok, 0 rows affected (0.01 sec) records: 0 duplicates: 0 warnings: 0 mysql> alter table t4 add index idx_name_age (name,age); query ok, 0 rows affected (0.01 sec) records: 0 duplicates: 0 warnings: 0

我们看一下 简单的 select 查询的执行计划: 这里的possible_keys 是idx_name,idx_name_age ,实际优化器选择的索引是idx_name

mysql> explain select * from t4 where name = 'bb'; ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- | 1 | simple | t4 | null | ref | idx_name,idx_name_age | idx_name | 83 | const | 1 | 100.00 | null | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- 1 row in set, 1 warning (0.00 sec)

那么如何查询数据库重复的索引呢? 目前市面上有3种方式:
利用第三方工具:
1)pt: pt-duplicate-key-checker
2)mysql untility: mysqlindexcheck
(上面这2中工具的方法使用都十分简单,大表哥就不展开介绍了,感兴趣的同学可以自行查阅)

3)或者查询sys数据库下面的试图 sys.schema_redundant_indexes

我们可以看到
贴心的mysql大叔建议我们 使用语句 sql_drop_index: alter table testdb.t4 drop index idx_name 来删除这个重复的索引。

mysql> select * from sys.schema_redundant_indexes where table_name = 't4'\g *************************** 1. row *************************** table_schema: testdb table_name: t4 redundant_index_name: idx_name redundant_index_columns: name redundant_index_non_unique: 1 dominant_index_name: idx_name_age dominant_index_columns: name,age dominant_index_non_unique: 1 subpart_exists: 0 sql_drop_index: alter table `testdb`.`t4` drop index `idx_name` 1 row in set (0.00 sec)

2)我们再来看一下 key_len 这个属性

key_len 代表了索引的长度: 我们看 idx_name 这个索引的长度为83。

mysql> explain select * from t4 where name = 'bb'; ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- | 1 | simple | t4 | null | ref | idx_name,idx_name_age | idx_name | 83 | const | 1 | 100.00 | null | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------- 1 row in set, 1 warning (0.00 sec)

这个83是如何计算的呢? 我们知道 idx_name 只有单键值是 name varchar(20) , 每个字符根据数据库编码不一样,像是utf8mb3是占3个字节,
像是utf8mb4是占4个字节,为了支持一些emjo的表情符号。 大表哥的这个表就是 utf8mb4 的。

所以目前 是 每个字符占有4个字节 * 20的长度 = 80 目前这个索引的len 是80

并且还可以看到这个字段是允许为空的 , 这个默认是 len 1 ,
并且name这个字段是变长字段 , 这个默认是 len 2

综上所述: 20 * 4 1 2 = 83 与执行计划中的 key len 的长度是相符合的

mysql> show create table t4\g *************************** 1. row *************************** table: t4 create table: create table `t4` ( `id` int(11) not null, `name` varchar(20) collate utf8mb4_0900_as_cs default null, `age` int(11) default null, primary key (`id`), key `idx_name` (`name`), key `idx_name_age` (`name`,`age`) ) engine=innodb default charset=utf8mb4 collate=utf8mb4_0900_as_cs 1 row in set (0.00 sec)

我们可以以此类推 看一下了另外一个索引的长度:idx_name_age

name: 20* 4 2 1 = 83
age: (int 默认占4个字节 ) 4 (可以为null) 1 = 5

name age = 83 5 =88

mysql> explain select * from t4 use index(idx_name_age) where name = 'bb' and age =1; ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- | 1 | simple | t4 | null | ref | idx_name_age | idx_name_age | 88 | const,const | 1 | 100.00 | using index | ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- 1 row in set, 1 warning (0.00 sec)

3)我们再来看一下 ref 这个属性

执行计划中ref这个属性就是表示 where 后面所触发的连接条件: 可以是表与表的连接,也可以是表与常量值的连接。

我们举例来看一下: where name = ‘bb’ and age =1; 其中 ‘bb’ 和 1 都是常量,索引对应的ref是 const,const

mysql> explain select * from t4 use index(idx_name_age) where name = 'bb' and age =1; ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- | 1 | simple | t4 | null | ref | idx_name_age | idx_name_age | 88 | const,const | 1 | 100.00 | using index | ---- ------------- ------- ------------ ------ --------------- -------------- --------- ------------- ------ ---------- ------------- 1 row in set, 1 warning (0.00 sec)

我们再来看一下: 表与表的连接: 我们可以看到 ref 是 testdb.a.id

mysql> explain select * from t4 a, t4 b where a.id = b.id; ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------------- ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------------- ------ ---------- ------------- | 1 | simple | a | null | index | primary | idx_name_age | 88 | null | 1 | 100.00 | using index | | 1 | simple | b | null | eq_ref | primary | primary | 4 | testdb.a.id | 1 | 100.00 | null | ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------------- ------ ---------- ------------- 2 rows in set, 1 warning (0.00 sec)

也可以是函数的连接条件:ref 是 func

mysql> explain select * from t4 a, t4 b where a.id = lower(b.id); ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------ ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------ ------ ---------- ------------- | 1 | simple | b | null | index | null | idx_name_age | 88 | null | 1 | 100.00 | using index | | 1 | simple | a | null | eq_ref | primary | primary | 4 | func | 1 | 100.00 | using where | ---- ------------- ------- ------------ -------- --------------- -------------- --------- ------ ------ ---------- ------------- 2 rows in set, 1 warning (0.00 sec)

4)我们再来看一下 rows 和 filtered 这个属性

rows : 这个表示通过全表或者索引扫描的预估的行数
filtered: 表示某列通过条件过滤的后所能筛选的比率。

值得注意的是,这2个属性的值都是预估的值,精确程度取决于表的统计信息和列上直方图的采样信息。

关于表和索引的统计信息 可以参考之前的文章: https://www.modb.pro/db/336054

关于列的直方图的信息 可以参考之前的文章: https://www.modb.pro/db/336688

5)我们最后再来看一下 extra 这个属性

extra列表示的是补充的额外的一些关于这个sql执行计划的一些信息。 这个extra 的内容有好几十种。

我们就来简单的看一下,常见的一些extra。

no tables used: 没有实际的去访问任何一张表

mysql> explain select now(); ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- | 1 | simple | null | null | null | null | null | null | null | null | null | no tables used | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- 1 row in set, 1 warning (0.00 sec)

impossible where: 1 = 2 是个伪命题, mysql大叔的优化器会直接 : say no!!

mysql> explain select * from t4 where 1 = 2; ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------------ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------------ | 1 | simple | null | null | null | null | null | null | null | null | null | impossible where | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------------ 1 row in set, 1 warning (0.00 sec)

using index: 覆盖索引, 不回表,mysql 大叔喜欢的类型

mysql> explain select name from t4 where name = 'b'; ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------------- | 1 | simple | t4 | null | ref | idx_name,idx_name_age | idx_name | 83 | const | 1 | 100.00 | using index | ---- ------------- ------- ------------ ------ ----------------------- ---------- --------- ------- ------ ---------- ------------- 1 row in set, 1 warning (0.00 sec)

using index condition: 索引下推, mysql大叔优化器的宗旨是 能在innodb 存储引擎层解决问题的,就不会放到mysql server 层来解决。

我们看一下这个例子: where name like ‘jason%’ and age = 2;

索引 key(name,age) 这个中 name like ‘jason%’ 不是一个等值的查询,是一个边界前闭后开的扫描, 这个时候mysql 大叔的优化器 不会把 name like 'jason%'扫描到所有数据拿到
server 层与 进行 与 age = 2的过滤
而是 在innodb存储引擎 的存储引擎层直接进行 age = 2的过滤, 再把最终的结果返回给 server 层, 这个优化就叫做 索引下推

mysql> create table tab1 (id int not null primary key, name varchar(20),age int, job varchar(20), key(name,age)); query ok, 0 rows affected (0.02 sec) mysql> insert into tab1 (id,name,age,job) values (1,'jason',20,'dev'); query ok, 1 row affected (0.00 sec) mysql> insert into tab1 (id,name,age,job) values (2,'jason',30,'dba'); query ok, 1 row affected (0.01 sec) mysql> insert into tab1 (id,name,age,job) values (3,'jason',40,'pm'); query ok, 1 row affected (0.00 sec) mysql> explain select * from tab1 where name like 'jason%' and age = 2; ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ----------------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ----------------------- | 1 | simple | tab1 | null | range | name | name | 88 | null | 3 | 33.33 | using index condition | ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ----------------------- 1 row in set, 1 warning (0.00 sec)

我们也可以通过优化器的参数来打开和关闭这个 索引下推的功能
mysql 默认是 index_condition_pushdown=on

mysql> set optimizer_switch='index_condition_pushdown=off'; query ok, 0 rows affected (0.00 sec) mysql> desc select * from tab1 where name like 'jason%' and age = 2; ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ------------- | 1 | simple | tab1 | null | range | name | name | 88 | null | 3 | 33.33 | using where | ---- ------------- ------- ------------ ------- --------------- ------ --------- ------ ------ ---------- ------------- 1 row in set, 1 warning (0.00 sec)

using where: 表示sql 语句需要进行 server 层的过滤

where 条件中 :job 列上不存在索引

mysql> explain select * from tab1 where name = 'jason' and job = 'dev'; ---- ------------- ------- ------------ ------ --------------- ------ --------- ------- ------ ---------- ------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------- ------ ---------- ------------- | 1 | simple | tab1 | null | ref | name | name | 83 | const | 3 | 33.33 | using where | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------- ------ ---------- ------------- 1 row in set, 1 warning (0.00 sec

using filesort: 需要在server层进行内存或者文件排序的sql

mysql大叔的优化器 不是很喜欢这个,应该在表设计中规避

mysql> explain select * from tab1 order by job desc; ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- | 1 | simple | tab1 | null | all | null | null | null | null | 3 | 100.00 | using filesort | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ---------------- 1 row in set, 1 warning (0.00 sec)

using temporary: 需要在server层进行分组group by ,去重distinct 等操作的sql。

mysql大叔的优化器 不是很喜欢这个,应该在表设计中规避

mysql> explain select job,count(1) from tab1 group by job; ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ----------------- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | extra | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ----------------- | 1 | simple | tab1 | null | all | null | null | null | null | 3 | 100.00 | using temporary | ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ----------------- 1 row in set, 1 warning (0.00 sec)

最后到这里, mysql 执行计划入门理解 上篇和下篇全部介绍完毕了。 开发和运维的同学,觉得有感兴趣的话,可以转发给你们周围的小伙伴!

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
1人已赞赏
【米乐app官网下载的版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

网站地图