Sql优化器究竟帮你作了哪些工做?

上一篇,咱们介绍了《DB——数据的读取和存储方式》,这篇聊聊sql优化器的工做。程序员

关系型数据库的一大优点之一,用户无需关心数据的访问方式,由于这些优化器都帮咱们处理好了,但sql查询优化的时候,我不得不要对此进行关注,由于这牵扯到查询性能问题。sql

有经验的程序员都会对一些sql优化了如指掌,好比咱们常说的最左匹配原则,非BT谓词规避等等,那么优化器是如何肯定这些的?以及为什么必定要最左匹配,最左匹配的原理是什么,你是否有深刻了解?数据库

这一篇咱们就经过一些实例来剖析优化器作了哪些工做,以方便咱们更好的优化SQL查询。数组

本篇你能够知道:缓存

  1. sql的访问路径是什么函数

  2. 优化器如何肯定最优访问路径性能

  3. 最左匹配的原则依据是什么fetch

  4. 如何有效的评估sql命中行数优化

 

示例table:spa

CREATE TABLE test (
​
  id int(11) NOT NULL AUTO_INCREMENT,
​
  user_name varchar(100) DEFAULT NULL,
​
  sex int(11) DEFAULT NULL,
​
  age int(11) DEFAULT NULL,
​
  c_date datetime DEFAULT NULL,
​
  PRIMARY KEY (id),
​
  # 索引
​
  KEY id_name_sex (id,user_name,sex),
​
  KEY name_sex_age (user_name,sex,age)
​
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;
​
​

 

1、访问路径

在SQL语句可以被真正执行以前,优化器必须首先肯定如何访问数据。这包括:应该使用哪个索引,索引的访问方式如何,是否须要辅助式随机读,等等。

从一条SQL,到优化器优化,再到引擎进行数据查询,落地到数据的存储页面,这是一个访问路径肯定的过程。

2、谓词

谓词就是咱们常说的where子句中的一个或多个搜索参数组成。谓词表达式是索引设计的主要入手点,若是一个索引可以知足select查询语句的全部谓词表达式,那么优化器就可能创建一个高效的访问路径。

select * from test where id =1 and user_name like ’test%’

好比,上述查询 中,where后面的搜索参数,id 和user_name 就是谓词。

 

3、索引片

索引片即表明谓词表达式所肯定的值域范围,而访问路径的成本很大程度上取决于索引片的厚度。

索引片越厚,须要扫描的索引页就越多,须要处理的索引记录也越多,并且最大的开销仍是来自于须要对标进行同步读操做。相反,索引片比较窄,就会显著减小索引访问的那部分开销,同时会有更少的表同步读取上。

同步读是一个随机IO操做,单次的读取就要耗费10ms左右的时间。这个咱们在上篇有说明。

 

好比:

//会匹配到5个数据
​
sql1:select * from test where sex=1;
​
// 匹配到2个数据
​
sql2:select * from test where sex=1 and age <10;

 

所以咱们须要经过谓词来肯定索引片的厚度,过滤的值域范围越少,索引片厚度就越窄。那么谓词必定就能匹配到索引么,或者说匹配的规则是什么?

 

4、匹配列&过滤列

谓词不必定都能匹配到索引,可以匹配上的,咱们称之为匹配列。此时它能够参与索引片的定义。

只有匹配列和过滤列能够参与索引片的定义和过滤,其余不可。

咱们来看下谓词匹配的定义:

检查索引列,从头至尾依次检查索引列,查看如下规则:

  1. 在where子句中,该列是否至少拥有一个足够简单的谓词与之对应?若是有,则这个列就是匹配列。若是没有,那么这个列及其后面的索引列都是非匹配列。

  2. 谓词是不是一个范围谓词,若是是,那么剩余的索引列都是非匹配列。

  3. 对于最后一个匹配列以后的索引列,若是拥有一个足够简单的谓词与其对应,那么该列为过滤列。

 

一、示例

select * from test where user_name=’test1’ and sex>0 and age =10

发现索引id_name_sex

  1. 逐行检查其索引列(id,user_name,sex)

  2. 首先检查 id,发现where后面的谓词没有与之对应,则 这个索引列以及后面的索引列都是非匹配列

  3. 索引id_name_sex匹配结束,无匹配列

发现索引name_sex_age

  1. 逐行检查其索引列(user_name,sex,age)

  2. 首先检查 user_name,发现where后面的 谓词 user_name 有与之对应,认定此列为匹配列

  3. 检查索引字段sex,发现where后面有谓词sex与之对应,认定此列为匹配列,因为谓词sex是范围谓词,则剩余的索引为非匹配列。

  4. 索引列age 是在最后一个匹配列sex 以后,而又有谓词age 与之对应,所以此列 为过滤列,

 

经过这个示例,咱们最终肯定了:

  • 匹配索引: name_sex_age

  • 匹配列: user_name,sex

  • 过滤列: age

 

咱们查看下 explain ,和咱们分析的对应。

 

二、肯定匹配列有什么用

肯定匹配列以后咱们能够知道当前的查询会用到哪些索引,以及匹配到该索引的哪些列,最终能够提早锁定数据的访问范围,为数据的读取节省读取压力。

相对于没用匹配到索引的查询,有匹配列的查询,条件过滤是前置的,而没有匹配到索引的查询,条件过滤是后置的,即全表扫描以后,再过滤结果,如此磁盘IO压力过大。

另外 “最左匹配”原则也是基于匹配列规则而来,为什么是最左匹配,除了B树的原理以外,还有一个重要的缘由,在核对匹配列的时候,是从头至尾依次检查索引列。

因此对因而否可以匹配到索引,where后面的谓词顺序不重要,重要的是索引列的顺序。

 

好比:

select * from test where user_name=’test1’ and sex>0 and age =10
​
select * from test where sex>0 and user_name=’test1’ and age =10
​
select * from test where age =10 and user_name='test1' and sex>0

 

均可以匹配到name_sex_age 索引

三、复杂谓词

like 谓词

若是值是%xx ,那么将会选择全索引扫描,不参与索引匹配,若是是xx%,这会参与索引匹配,选择索引片扫描。

OR操做符

即使是简单的谓词,若是它们与其余谓词之间为OR操做,对优化器而言是异常困难的,除非在多索引访问,才有可能参与到一个索引片的定义,尽可能不要用。

假设一个谓词的断定结果为false,而此时不检查其余谓词就不能肯定的将一行记录排除在外,那么这类谓词对优化器而言就是十分困难的。

BT谓词

好比只有and 操做符,那么全部的简单谓词均可以称谓BT谓词,也就是好的谓词,除非访问路径是一个多索引扫描,不然只有BT谓词能够参加定义索引片。

谓词值不肯定

好比谓词的值采用了函数,或者参与了计算,优化器在作静态SQL绑定的时候,每次都须要从新计算选择,没法缓存,耗费大量的CPU,也没法参与索引列的匹配。

 

5、过滤因子

匹配列肯定了使用那些索引列,但索引片的厚度(也就是预计要访问多少行),尚未估算出来。此处须要进行经过过滤因子来肯定。

过滤因子描述的谓词的选择性,即表中知足谓词条件的记录行数所占用的比例,依赖于列值分布状况。

 

一、单个谓词的过滤因子

好比,咱们的的test表有10000条记录,谓词user_name 匹配了 一个索引列,其过滤因子是0.2%(1/不一样user_name数量=user_name中有500个不一样值的比率),则意味着查询结果会包含20行的记录。

 

select * from test where user_name=’test’

二、组合谓词的过滤因子

当有多个谓词符合匹配列的时候,咱们能够经过单个谓词的过滤因子推导出组合过滤因子。通常的公式是:

组合过滤因子=谓词1过滤因子*谓词2过滤因子....

好比以下查询

select * from test where user_name=’test’ and sex=1 and age =10

包含3个谓词,user_name、sex、age、其中user_name有500个不一样的值,sex有2个不一样的值,age有40个不一样的值。

则每一个谓词的过滤因子:

FF(user_name) =1/500*100 =0.2%

FF(sex) =1/2*100=50%

FF(age) =1/40*100=2.5%

组合过滤因子=0.2%*50%*2.5%=0.0025%

 

经过以上组合过滤因子,能够推算出最终的结果集=10000*0.0025%=0.25 ~=1

 

经过以上过滤因子评估以后,咱们能够看到,最终须要查找的结果集只须要获取1行就够了,这对数据库的磁盘访问有很高的性能提高。

这也是优化器在评估可选访问路径成时,必须先进行过滤因子评估的重要性。

 

6、排序

物化结果集意味着经过执行必要的数据库访问来构建结果集。最好状况下,只须要返回一条记录,而最坏的状况下须要返回多条记录,须要发起大量的磁盘读取。而排序就是其中一种。

在如下状况中,一次fetch调用只须要物化一条记录,不然对结果进行排序的时候就须要物化整个结果集。

  • 没有排序需求,好比order by,group by 等。

  • 虽然须要排序知足如下两个条件:

  1. <!--存在一个索引知足结果集的排序需求,好比上述的(id_name_sex) 或者(name_sex_age)-->

  2. <!--优化器决定以传统的方式使用这个索引,即访问第一条知足条件的索引行并读取相应的表行,而后访问第二条知足条件的索引行并读取相应的表行,依次类推。-->

  3. <!--好比使用索引(name_sex_age)时候,select * from test where user_name=’test’ order by sex ,此时在索引中,结果集基于sex自己就是有序的-->

 

7、最后

sql优化器作的不只仅是你这些工做,但索引片的大小的预估,以及访问路径的肯定倒是它最重要的工做,后续咱们再继续介绍。

 

-----------------------------------------------------------------------------

想看更多有趣原创的技术文章,扫描关注公众号。

相关文章
相关标签/搜索