5-02 Explain详解与索引最佳实践
2022-12-09 23:43:39 0 举报
02 Explain详解与索引最佳实践
作者其他创作
大纲/内容
<b>Explain详解</b>
<b>Explain工具介绍</b>
1. 使用Explain关键字可以模拟优化器执行SQL语句,分析你的查询语句或是结构的性能瓶颈
2. 在select 语句之前添加explain关键字,MySQL 会在查询上设置一个标记,执行查询会返回执行计划的信息,并不是执行这条SQL
<b>Explain分析示例</b>
<b>官方文档</b>:<a class="link" target="_bank" href="https://dev.mysql.com/doc/refman/5.7/en/explain-output.html">https://dev.mysql.com/doc/refman/5.7/en/explain-output.html</a>
<b>应用示例</b>:
示例表:<font color="#9e9e9e">(Explain两个变种、Explain中的列中的示例都会用到)</font>
在查询中的每个表会输出一行,如果有两个表通过 join 连接查询,那么会输出两行
<b>Explain两个变种</b>
1、<font color="#2196f3">explain extended</font>
在<b><font color="#4caf50">explain</font></b>的基础上额外<b>提供</b>一些<b>查询优化</b>的<b>信息</b>
相比 explain 多了个 <font color="#000000" style=""><b>filtered </b>字段</font>:
是一个半分比的值
<b>rows*filtered/100</b> 可以<b>估算</b>出将要<b>和</b> explain 中<b>前一个表</b>进行<b>连接的行数</b>
如下图所示:
2、<font color="#2196f3">explain partitions</font>
相比 explain 多了个 <b>partitions </b>字段
如果查询是基于分区表的话,会显示查询将访问的分区
如下图所示:
<b>3、<font color="#2196f3">紧随其后的</font> <font color="#2196f3">show warnings</font></b>
<b>紧随其后</b>通过 <b>show warnings</b> 命令可以<b>得到优化后的查询语句</b>,从而看出优化器优化了什么
如下图示例1:
如下图示例2:
<b>Explain中的列</b>
<b><font color="#e57373"> id列</font></b>
<b><font color="#e57373">id列</font></b>的<b><font color="#e57373">编号</font></b>是<b><font color="#e57373">select的序列号</font><font color="#000000">,</font><font color="#e57373">有几个select</font></b>就<b><font color="#e57373">有几个id</font></b>
<b><font color="#e57373">id的编号</font></b>是按<b><font color="#e57373">select出现的顺序增长</font></b>的
<b>id编号越大</b>执行<b>优先级越高</b>
<b>id相同</b>时,则<b>从上往下</b>执行
<b>id为NULL</b>时,则<b>最后</b>执行
<b><font color="#e57373">select_type列</font></b><br>
<b><font color="#e57373">表示</font></b>对应行是<b><font color="#e57373">简单查询?</font></b>还是<b><font color="#e57373">复杂查询?</font></b>
<b><font color="#2196f3">1)simple:简单查询。查询不包含子查询和union</font></b>
如下图示例:
<b><font color="#2196f3">2)primary:复杂查询中最外层的select查询</font></b>
如下图示例:
<font color="#2196f3"><b>3)</b></font><b><font color="#2196f3">subquery</font></b><font color="#2196f3"><b>:包含在select中的子查询(不在from子句中)</b></font>
如下图示例:
<b><font color="#2196f3">4)derived:包含在from子句中的子查询。</font></b>
MySQL会将包含在<b><font color="#000000">from</font></b>子句中<font color="#000000">的<b>子查询</b></font>的<b>结果存放</b>在一个<b>临时表</b>中,也称为<b>派生表</b>(derived的英文含义)
如下图示例1:<b>关闭5.7新特性-对衍生表的合并优化</b>
如下图示例2:
如下图示例3:<b>开启5.7新特性-对衍生表的合并优化</b>
<b><font color="#2196f3">5)union:</font></b><font color="#2196f3"><b>在union中的第二个和随后的select</b></font>
如下图示例:
<b><font color="#e57373">table列</font></b>
这一列表示 explain 的一行<b><font color="#2196f3">正在访问哪个表</font></b>
如下图示例:
当 <b><font color="#2196f3">from 子句中有子查询</font></b>时,table 列<font color="#2196f3"><b>是 <derivenN></b></font> 格式,<b><font color="#2196f3">表示</font></b>当前查询<b><font color="#2196f3">依赖 id=N 的那行的查询</font></b>,需要先执行 id=N 的查询
如下图示例:
当 <b><font color="#2196f3">union </font></b>时,UNION RESULT 行的 table 列的<b><font color="#2196f3">值为<union 1,2></font></b>,<b><font color="#2196f3">1和2表示</font></b>参与union的<b><font color="#2196f3"> select 行id</font></b>
如下图示例:
<b><font color="#e57373">type列</font></b>
这列表示<b><font color="#2196f3">关联类型</font></b>或<b><font color="#2196f3">访问类型</font></b>
即:MySQL决定<b>如何查找表中的行</b>,<b>查找</b>数据<b>行记录</b>的<b>大概范围</b>
<b><font color="#2196f3">依次从最优到最差</font></b>分别为:
<b style=""><font color="#2196f3">NULL</font><font color="#000000"> > </font><font color="#2196f3">system </font></b><b>> </b><font color="#2196f3" style="font-weight: bold;">const </font><b>> </b><font color="#2196f3" style="font-weight: bold;">eq_ref </font><b>></b><font color="#2196f3" style="font-weight: bold;">ref </font><b>> </b><font color="#2196f3" style="font-weight: bold;">range </font><b>> </b><font color="#2196f3" style="font-weight: bold;">index </font><b>> </b><font color="#2196f3" style="font-weight: bold;">ALL</font>
一般<b><font color="#f44336">得保证查询达到 </font><font color="#2196f3">range</font><font color="#f44336"> 级别</font></b>,<b><font color="#d32f2f">最好达到 </font><font color="#2196f3">ref </font><font color="#d32f2f">级别</font></b>
<b><font color="#2196f3">1)NULL:MySQL能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。</font></b>
如下图所示:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表。
<b><font color="#2196f3">2)system,const:</font></b><br>
MySQL能<b>对查询的某部分</b>进行<b>优化</b>并将其<b>转化成一个常量</b>(可以看<b>show warnings</b> 的结果)
<b><font color="#2196f3">const </font></b>用于<b>primary key </b>或 <b>unique key</b> 的所有列<b>与常数比较时</b>,所以表<b>最多匹配到一行</b>,<b>读取1次</b>,<b>速度比较快</b>
<b>system 是 const 的特例</b>,<b>表里只有一条元组匹配</b>时为 <b><font color="#2196f3">system</font></b>
如下图所示:
<b><font color="#2196f3">3)eq_ref:</font></b>
<font color="#2196f3"><b>primary key 或 unique key</b> <b>索引</b></font>的所有部分<b><font color="#2196f3">被连接使用</font> </b>,<b>最多<font color="#2196f3">只会返回一条符合条件的记录</font></b>
<b>这</b>可能<b>是</b>在<b>const 之外最好</b>的<b>关联类型</b>了,<b>简单</b>的 select <b>查询不会出现这种 type</b>
如下图所示:
<b><font color="#2196f3">4)ref:</font></b>
相比 <b><font color="#4caf50">eq_ref</font></b>,<font color="#2196f3"><b>不使用唯一索引</b></font>,而是<b><font color="#2196f3">使用普通索引</font></b>或者<b><font color="#2196f3">唯一性索引的部分前缀</font></b>
<b><font color="#2196f3">索引</font>要和<font color="#2196f3">某个值</font>相比较</b>,<font color="#2196f3"><b>可能</b></font>会找到<b><font color="#2196f3">多个符合条件的行</font></b>
1. 简单 select 查询,name是普通索引(非唯一索引)
如下图所示:
2. 关联表查询,idx_film_actor_id 是film_id 和 actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分
如下图所示:
<b><font color="#2196f3">5)range:</font></b>
<b><font color="#2196f3">范围扫描</font></b>通常出现在 <b><font color="#00bcd4">in()</font></b>, <b><font color="#2196f3">between</font></b> ,<b><font color="#2196f3">></font></b> ,<b><font color="#2196f3"><</font></b>, <b><font color="#2196f3">>=</font></b> 等操作中
<b><font color="#2196f3">使用</font></b>一个<b><font color="#2196f3">索引</font></b>来<b><font color="#2196f3">检索给定范围</font></b>的行
如下图所示:
<b><font color="#2196f3">6)index:</font></b>
<b><font color="#9e9e9e">扫描全索引就能拿到结果</font></b>
<b><font color="#2196f3">一般是扫描某个二级索引</font></b>
这种扫描<b><font color="#9e9e9e">不会从索引树根节点开始</font></b>快速查找
而是<b><font color="#2196f3">直接对二级索引的叶子节点遍历和扫描</font></b>,速度还是比较慢的
<b><font color="#2196f3">这种查询</font></b>一般为<b><font color="#2196f3">使用覆盖索引</font></b>
<b><font color="#2196f3">二级索引一般比较小</font></b>,所以这种通常比<b><font color="#4caf50">ALL</font></b>快一些
如下图所示:
<b><font color="#2196f3">7)ALL:</font></b>
即<b><font color="#2196f3">全表扫描</font></b>,<b><font color="#2196f3">扫描</font></b>你的<b><font color="#2196f3">聚簇索引(主键索引)</font></b>的<b><font color="#2196f3">所有叶子节点</font></b>
通常情况下这<b><font color="#2196f3">需要增加二级索引</font></b>来进行<b><font color="#2196f3">优化</font></b>了
如下图所示:
<b><font color="#e57373">possible_keys列</font></b>
<b><font color="#2196f3">表示</font></b>这一列显示查询<b><font color="#2196f3">可能使用哪些索引来查找</font></b>
<b>explain 时</b>可能出现<b> possible_keys 有值</b>,而<b> key 显示 NULL</b> 的情况
这种情况是<b><font color="#f57c00">因为</font>表中数据不多</b>,MySQL认为<b>索引</b>对此查询<b>帮助不大</b>,<b>选择了全表查询</b>
如果<b><font color="#2196f3">该列</font></b>是<b><font color="#2196f3">NULL</font></b>,则<b><font color="#2196f3">没有相关的索引</font></b>
在这种情况下,可以通过<b>检查 where 子句</b>看<b>是否</b>可以<b>创造</b>一个<b>适当的索引</b>来提高查询性能,然后用 explain 查看效果
<b><font color="#e57373">key列</font></b>
这一列显示 MySQL <b><font color="#2196f3">实际采用哪个索引</font></b>来<b><font color="#2196f3">优化</font></b>对该表的访问
如果<b><font color="#2196f3">没有使用索引</font></b>,则<font color="#2196f3"><b>该列</b></font>是 <font color="#2196f3"><b>NULL</b></font>
如果想强制 MySQL 使用或忽视 <b><font color="#4caf50">possible_keys </font></b>列中的索引引,在查询中使用 <font color="#2196f3"><b>forceindex</b></font>、<b><font color="#2196f3">ignore index</font></b>。<br>
<b><font color="#e57373">key_len列</font></b>
这一列<b><font color="#2196f3">显示了</font></b>mysql在<b><font color="#2196f3">索引里使用的字节数</font></b>
<b><font color="#2196f3">通过这个值可以算出</font></b>具体<b><font color="#2196f3">使用了索引中的哪些列</font></b>
举例来说:
<b>film_actor</b> 表的联合索引 <b>idx_film_actor_id</b> 由 <b>film_id </b>和 <b>actor_id</b> 两个<b>int列</b>组成,并且<b>每个int是4字节</b>
结果中的 <b>key_len=4 </b>可<b>推断出查询使用了第一个列</b>:<b>film_id 列</b>来执行索引查找
如下图所示:
<b><font color="#2196f3">key_len计算规则:</font></b><br>
<b>字符串</b>
<b>char(n)</b>:如果存汉字长度就是 3n 字节
<b>varchar(n)</b>:如果存汉字则长度是 3n + 2 字节,加的2字节用来存储字符串长度,因为varchar是变长字符串
<b>数值类型</b>
<b>tinyint</b>:1字节
<b>smallint</b>:2字节
<b>int</b>:4字节
<b>bigint</b>:8字节
<b>时间类型</b><br>
<b>date</b>:3字节<br>
<b>timestamp</b>:4字节
<b>datetime</b>:8字节
如果字段允许为 NULL,<b>需要<font color="#e57373">1字节</font>记录是否为 NULL</b>
<b>索引最大长度是768字节</b>,当字符串过长时,<b>MySQL</b>会<b>做</b>一个<b>类似左前缀索引的处理</b>,将<b>前半部分的字符</b>提取出来<b>做索引</b>
<b><font color="#e57373">ref列</font></b>
这一列<b>显示</b>了在key列<b>使用到的索引</b>中,<b>表查找值</b>所<b>用到的列或常量</b>
<b>常见的有</b>:<b><font color="#2196f3">const(常量)</font></b>,<b><font color="#2196f3">字段名(例:film.id)</font></b>
<b><font color="#e57373">rows列</font></b>
这一列是MySQL<b>估计要读取并检测的行数</b>,注意<font color="#f44336">这个不是结果集里的行数</font>
<b><font color="#e57373">Extra列</font></b>
这一列展示的是<b>额外信息</b>。常见的重要值如下:
<b><font color="#2196f3">1)Using index:</font><font color="#000000">使用覆盖索引</font></b>
覆盖索引定义:
mysql执行计划<b>explain结果</b>里的<b>key有使用索引</b>
如果<b><font color="#000000">select后面查询的字段</font></b>都<b>可以从</b>这个<b>索引的树中获取</b>,<b>这种情况</b>一般可以说是<b>用到了覆盖索引</b>
extra里一般都有<b><font color="#2196f3">using index</font></b>
覆盖索引一般针对的是<b>辅助索引,</b>整个<b>查询结果</b><b style=""><font color="#ff9800">只通过</font></b><b>辅助索引<font color="#ff9800">就能</font>拿到结果</b>,不需要通过辅助索引树找到主键,再通过主键去主键索引树里获取其它字段值<br>
如下图所示:
<b><font color="#2196f3">2)Using where:</font><font color="#000000">使用 where 语句来处理结果,并且查询的列未被索引覆盖</font></b>
如下图所示:
<b><font color="#2196f3">3)Using index condition:</font><font color="#000000">查询的列不完全被索引覆盖,where条件中是一个前导列的范围</font></b>
如下图所示:<br>
<b><font color="#2196f3">4)Using temporary:</font><font color="#000000">MySQL需要创建一张临时表来处理查询</font></b>
<b><font color="#2196f3">出现这种情况</font></b>一般是<b><font color="#2196f3">要进行优化</font></b>的,首先是想到<b><font color="#2196f3">用索引来优化</font></b>
1. actor.name 没有索引,此时创建了张临时表来distinct
如下图所示:
2. film.name 建立了 idx_name 索引,此时查询时 extra 是 using index,没有用临时表
如下图所示:
<b><font color="#2196f3">5)Using filesort:</font></b>
<b>使用外部排序</b>而不是索引排序,<b>数据较小时从内存排序</b>,<b>否则</b>需要<b>在磁盘</b>完成<b>排序</b>
这种情况下一般也是要<b style=""><font color="#000000">考虑使用索引来优化</font></b>的
1. actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录
如下图所示:
2. film.name建立了idx_name索引,此时查询时extra是using index
如下图所示:
<b><font color="#2196f3">6)Select tables optimized away:</font>使用某些聚合函数(比如 max、min)来访问存在索引的某个字段</b><br>
如下图所示:
<b>索引最佳实践</b>
示例表:<font color="#9e9e9e">(索引最佳实践中的示例都会用到)</font>
<b><font color="#2196f3">1. 全值匹配</font></b>
示例1:
示例2:
示例3:
<b><font color="#2196f3">2. 最左前缀法则</font></b>
如果<b>索引了多列</b>(即:<b>联合索引</b>),要遵守<b>最左前缀法则</b>
指的是<b>查询从联合索引的最左前列开始</b>并且不跳过索引中的列
如果<b>跳过了索引中的最左前列</b>,则此查询<b>不会使用联合索引</b>
示例1:遵守最左前缀法则,使用了索引
示例2:跳过了索引中的最左前列,不会使用联合索引
<b><font color="#2196f3">3. 不要在索引列上做任何操作(计算、函数、(自动or手动)类型转换)</font></b>
在<b>索引列上做操作会导致索引失效</b>而转向全表扫描
示例1:未在索引列上做操作,索引使用正常<br>
示例2:在索引列上进行类型转换,索引失效
示例3:
1) 给hire_time增加一个普通索引
2) 转化时间类型,索引失效
3) 改为时间范围查询,有可能会走索引
4) 还原最初的索引状态
<b><font color="#2196f3">4. 范围条件右边之后的列不能使用索引</font></b><br>
如下图所示:
<b><font color="#2196f3">5. 尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少 select * 语句</font></b>
示例1:使用select * 语句
示例2:使用覆盖索引
<b><font color="#2196f3">6. 使用不等于(!= 或者 <>),not in ,not exists 可能不会使用索引导致全表扫描</font></b>
<b>< 小于、 > 大于、 <=、>= 等</b>这些,<b>MySQL</b>内部<b>优化器会根据检索比例、表大小等多个因素整体评估是否使用索引</b>
如下图所示:
<b><font color="#2196f3">7. 使用 is null , is not null 一般情况下也无法使用索引</font></b><br>
如下图所示:
<font color="#2196f3"><b>8. like以通配符开头('$abc...')mysql索引失效会变成全表扫描操作</b></font><br>
如下图所示:<b>模糊左匹配</b>
<b><font color="#2196f3">9. 字符串不加单引号,索引失效</font></b>
如下图所示:<b>字符串不加单引号,索引失效</b>
<b><font color="#2196f3">10. 使用 or 或 in 时,不一定使用索引</font></b>
如下图所示:<b>使用or查询,未使用索引</b>
<b>MySQL</b>内部优化器会<b>根据检索比例、表大小等多个因素</b>整体<b>评估是否使用索引</b>
<b>查询优化方法</b>可参考 <b><font color="#4caf50">11. 范围查询优化</font></b>
<b><font color="#2196f3">11. 范围查询优化</font></b><br>
1)给<b>年龄字段</b>添<b>加索引</b>,但<b>范围查询</b>时并<b>未使用索引</b>
2)上边<b>没走索引原因</b>:
<b>MySQL</b>内部优化器会<b>根据检索比例</b>、<b>表大小</b>等<b>多个因素</b>整体<b>评估是否使用索引</b>
比如<b>上边的示例</b>,<b>可能</b>是由于<b>单次数据量查询过大</b>导致优化器最终<b>选择不走索引</b>
3)<b>优化方法</b>:可以将<b>大的范围拆分成多个小范围</b>
<b>索引使用总结:</b><br>
<b>各种 where 语句联合索引是否被使用总结:</b>
如下图所示:
l<b>ike KK% 相当于 =常量,%KK 和 %KK% 相当于 查询范围</b>
0 条评论
下一页