索引及之执行计划,部分的个人见解

分析执行计划

10.1 是什么?

优化器(算法)最终得出的,代价最低的,SQL语句的执行方案.

10.2 为什么要分析执行计划?

场景一: 分析比较慢的语句.
场景二: 上线新业务,可能会包含很多select update delete...,提前发现问题.

10.3 如何抓取执行计划

a. 抓取目标: select update delete

b. 抓取执行计划的方法 :

mysql> desc   select * from world.city where countrycode='CHN';


+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+-------+

10.4 执行计划的分析:

table  :  操作的表
type   :  操作类型(全表\索引) ,ALL  index   range  ref   eq_ref  const(system)
possible_keys : 有可能用的索引
key    : 真正要用是哪索引
key_len: 索引覆盖长度(联合索引)
rows   : 预估需要扫描的行数
Extra  : using where  using index  using index condition  using  filesort sort   using temp

对type(操作类型)的详解

状态的分为:
第一种:ALL: 全表的扫描(不走索引)
可能会用到全表扫描的案例:
mysql> explain   select * from world.city ;                                    #进行全表扫描
mysql> explain   select * from world.city where countrycode !='chn';             #进行不相等的查找
mysql> explain   select * from world.city where countrycode like '%hn%';          #进行模糊查找
mysql> explain   select * from world.city where countrycode not in ('chn','usa'); #使用in语句进行查找

第一部分:index 全索引进行扫描

需要扫描整颗索引树,才能得到想要的结果. 
desc select id ,countrycode from world.city;

第二部分:range 索引范围

使我们优化索引的底线,也是应用最多的
mysql> desc select * from city where id<10;
mysql> desc select * from city where countrycode like 'ch%';
mysql> desc select * from city where countrycode in ('CHN','USA');

第三部分:ref : 辅助索引等值查询

mysql> desc  select * from city where countrycode='CHN';

第四部分:eq_ref : 非驱动表,连接条件是主键或唯一键.

mysql> desc select a.name, b.name,a.countrycode,a.population  
from city  as a 
join country as b 
on a.countrycode=b.code where a.population<100;


在多表连接时,小结果集的表驱动大表
优化会自动判断查询语句中的谁做为驱动表更合适.有可能会出现选择错误.
我们可以通过left join 强制驱动表干预执行计划.

如何判断一条连接语句中,谁是驱动表

第一种:优化器的选择

mysql> desc  select * from city  join country on city.countrycode=country.code where city.population<100000   and country.SurfaceArea>10000000;

第二种:人为的选择:

mysql> select count(*) from  country where country.SurfaceArea>10000000;  
+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

mysql> select celect count(*) from city where city.population<1
+----------+
| count(*) |
+----------+
|      517 |
+----------+
1 row in set (0.01 sec)
补充: 如果 where后的列中都有索引,会选择结果集小的作为驱动表.

第五部分:const(system) 索引等值查询

mysql>  desc select * from city where id=1;
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | const | PRIMARY       | PRIMARY | 4       | const |    1 |   100.00 | NULL  |
+----+-------------+-------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
##完全利用索引进行查询  就是索引的等值的查询

key_len: 索引覆盖长度(联合索引)的详解

a. 介绍

(联合)索引覆盖长度
idx(a,b,c) ----> a (10) b(20) c(30)

b. 如何计算索引列的key_len
key_len 和每个列的最大预留长度(字节)有关.

数据类型    utf8mb4       没有 not null (yes就加一)
tinyint      1              1    
int          4              1
char(10)     4*10           1
varchar(10)  4*10+2         1 




mysql> desc city;
+-------------+----------+------+-----+---------+----------------+
| Field       | Type     | Null | Key | Default | Extra          |  字符集为:utf8mb4 
+-------------+----------+------+-----+---------+----------------+
| ID          | int      | NO   | PRI | NULL    | auto_increment |  ####索引长度:4
| Name        | char(35) | NO   | MUL |         |                |  ####索引长度:35X4
| CountryCode | char(3)  | NO   | MUL |         |                |  ####索引长度:3X4
| District    | char(20) | NO   | MUL |         |                |  ####索引长度:20X4
| Population  | int      | NO   |     | 0       |                |  ###索引长度:4
+-------------+----------+------+-----+---------+----------------+

c. 联合索引应用细节

idx(a,b,c)    ---->   a  ab   abc 
完全覆盖 : 
    a=  and b=   and c=
    a=  and c=  and  b=  等值打乱顺序的
    a=  and b=  and  c范围 
    a=  anb b字符范围 and c=
        
部分覆盖 
    a=  and b= 
    a= 
    a=  and c=
    a=  anb b数字范围 and c=
完全不覆盖  bc  --> bc  b   
    b  
    c
    bc
    cb 

优化案例:
idx(k1,num,k2)
1. mysql> desc select * from t100w where k1='Vs' and num<27779 and k2='mnij'

优化方案: 修改索引为idx(k1,k2,num)

extra :索引的使用率

1.1出现using where问题的解决思路:

using index  ##最优质的的索引状态 使用了索引覆盖扫描 
using where  ###部分用到索引,部分么有用到索引    使用where回表扫描数据行,说明目标表的索引没有设计好 
解决问题的思路:
第一步:回到出问题的表  table----->获取到出问题的表
第二步:看原始查询语句中的wherer条件列
第三步:查询列的索引情况: ---->  show index from tl  
第四步:按需求进行优化索引

1.2出现using filesort (使用额外排序)问题的解决思路

第一步:  ##table---->获取到出问题的表
第二步: 查看原始语句中的  order by group by distinct  等
第三步: 查看列的索引情况
第四步: 按需进行优化索引

优化案例

mysql> desc select *from city where countrycode='CHN' order by population;
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key         | key_len | ref   | rows | filtered | Extra          |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode   | CountryCode | 3       | const |  363 |   100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+-------------+---------+-------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
第一步:查看列的索引情况:
mysql> show index from city;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| city  |          0 | PRIMARY     |            1 | ID          | A         |        4188 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| city  |          1 | CountryCode |            1 | CountryCode | A         |         232 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+------
第二步:按照所需的条件进行创建索引:创建联合索引
mysql> alter table city add index idx_c_p(countrycode,population);    ###创建联合索引
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> desc select *from city where countrycode='CHN' order by population;  ###查看索引执行计划
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys       | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | city  | NULL       | ref  | CountryCode,idx_c_p | idx_c_p | 3       | const |  363 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------------+---------+---------+-------+------+----------+-------+    

1.3出现using temp 的情况分析

a. 条件范围是不是过大. 
b. having  order by   ##这些条件必然出现using temp 进行额外排序
c. 子查询
大几率开发需要改写语句了.

扩展项: 关于索引的优化器算法:ICP MRR

第一部分: ICP : Index Condition Pushdown(优化器的选择)

优化器算法:

    a. 查询优化器算法:
    mysql> select @@optimizer_switch;
    b. 设置优化器算法: 
    mysql> set global optimizer_switch='index_condition_pushdown=off';
    hits方式: 
    https://dev.mysql.com/doc/refman/8.0/en/optimizer-hints.html
    配置文件: 
    my.cnf
例子 : 
mysql> set global optimizer_switch='index_condition_pushdown=off';

mysql> desc select * from t100w where k1='Vs'  and num<27779 and k2='mnij';
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | t100w | NULL       | range | idx           | idx  | 14      | NULL |   29 |    10.00 | Using where |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> set global optimizer_switch='index_condition_pushdown=on';

mysql> desc select * from t100w where k1='Vs'  and num<27779 and k2='mnij';
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | t100w | NULL       | range | idx           | idx  | 14      | NULL |   29 |    10.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)


压测:a.  开ICP 2000次语句压测  索引顺序不调整
mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=100 --iterations=1 --create-schema='test' \
--query=" select * from t100w where k1='Vs'  and num<27779 and k2='mnij'" engine=innodb \
--number-of-queries=2000 -uroot -p123 -verbose

4.580 seconds
4.569 seconds
4.431 seconds
4.433 seconds
4.391 seconds

b.  关 ICP 2000次语句压测  索引顺序不调整
5.327 
5.516
5.267
5.330
5.293 seconds

c. 索引顺序优化 压测 
4.251
4.143 

11.2 MRR  
https://dev.mysql.com/doc/refman/8.0/en/mrr-optimization.html

12. 索引应用规范

12.1 建立索引的原则(DBA运维规范)
(1) 必须要有主键,业务无关列。
(2) 经常做为where条件列  order by  group by  join on, distinct 的条件(业务:产品功能+用户行为)
(3) 最好使用唯一值多的列作为索引列,如果索引列重复值较多,可以考虑使用联合索引
(4) 列值长度较长的索引列,我们建议使用前缀索引.
mysql> select count(distinct left(name,19)) from city;
(5) 降低索引条目,一方面不要创建没用索引,不常使用的索引清理,percona toolkit(xxxxx)
(6) 索引维护要避开业务繁忙期,建议用pt-osc。 
(7) 联合索引最左原则



12.2 不走索引的情况(开发规范)
11.2.1 没有查询条件,或者查询条件没有建立索引
select * from t1  ;
select * from t1 where id=1001 or 1=1;

作业: 
SQL审核和审计. yearning.io  github, inception



12.2.2 查询结果集是原表中的大部分数据,应该是15-25%以上。
查询的结果集,超过了总数行数25%,优化器觉得就没有必要走索引了。
MySQL的预读功能有关。

可以通过精确查找范围,达到优化的效果。
1000000

>500000 and 


12.2.3 索引本身失效,统计信息不真实(过旧)

索引有自我维护的能力。
对于表内容变化比较频繁的情况下,有可能会出现索引失效。
一般是删除重建

现象:
有一条select语句平常查询时很快,突然有一天很慢,会是什么原因
select?  --->索引失效,统计数据不真实

innodb_index_stats  
innodb_table_stats  

mysql> ANALYZE TABLE world.city;


12.2.4 查询条件使用函数在索引列上,或者对索引列进行运算,运算包括(+,-,*,/,! 等)
例子:
错误的例子:select * from test where id-1=9;
正确的例子:select * from test where id=10;
算术运算
函数运算
子查询

12.2.5 隐式转换导致索引失效.这一点应当引起重视.也是开发中经常会犯的错误.
mysql> desc select * from b where telnum=110;
mysql> desc select * from b where telnum='110';

12.2.6 <> ,not in 不走索引(辅助索引)


12.2.7 like "%_" 百分号在最前面不走

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容