赞
踩
访问方法/访问类型:
默认情况下是从二级索引中读取到一条记录后,就会进行回表,而MRR优化可以先读取一部分二级索引记录,将它们的主键值排好序后再统一回表
索引合并:
没有sort-intersection,sort-union针对的是单独根据搜索条件从某个二级索引中获取的记录数比较少的场景。因为根据单个索引获取的记录比较少,但是用了or连接,导致还是需要进行全表查询,所以采用union优化,那么这种情况下记录比较少自然排序开销也不大
而intersection合并是因为单独某个索引查询出的记录比较多,进行回表次数比较多,所以才需要合并索引来减少需要回表的记录数。这种情况下单个索引查出的记录多,使用排序开销也比较大,所以没有sort-intersection
内连接和外连接的区别:
由于被驱动表可能会被访问多次,因此可以为被驱动表建立合适的索引以加快查询速度
如果被驱动表非常大,多次访问被驱动表可能导致很多次的磁盘IO(因为默认情况下是驱动表没找到一条记录就去查询被驱动表,如果被驱动表很大,那么可能会导致被驱动表前面引入的页面在后面被淘汰了,那么驱动表下一条记录进行连接时又要重新去磁盘中加载),因此引入join buffer,一次性读取多条驱动表记录放入其中。join buffer只会放置查询列表中的列和过滤条件中的列。
InnoDB提供了两种存储统计数据的方式,分别是永久性的存储统计数据和非永久性的存储统计数据(服务器关闭后清除)。
InnoDB默认以表为单位来收集和存储统计数据,可以通过在创建表时指定STATS_PERSISTENT属性来指定(1则存储在磁盘,0则存在内存,默认是存在磁盘)。这些统计数据实际上是存放在mysql系统数据库的innodb_table_stats和innodb_index_stats表中。
数据库名
表名
最后更新时间
记录数(估计值,按照一定的算法从聚簇索引中选取几个叶子结点页面,统计每个页面中包含的记录数量,计算出一个页面平均包含的记录数量,再将其乘以全部叶子节点的数量)
聚簇索引占用的页面数量:需要统计聚集索引对应的叶子结点段和非叶子结点段分别占用的页面数量
其他索引占用的页面数量:需要统计其他索引对应的叶子结点段和非叶子结点段
数据库名
表名
索引名
最后更新时间
统计项的名称
对应的统计项的值
为生成统计数据而采样的页面数量:在计算某些索引列中包含多少个不重复的值时,需要对一些叶子结点页面进行采样
对应的统计项的描述
开启innodb_stats_auto_recalc:默认是打开的,决定了服务器是否自动重新计算统计数据。每个表都维护了一个变量,记录着对该表进行增删改的记录条数,**如果发生改动的记录数量超过了表大小的10%,并且自动重新计算统计数据的功能是打开的,那么服务器会重新计算一次统计数据。**这个过程是异步执行的。
手动调用analyze table语句来更新统计信息,这个过程是同步的。
innodb_table_stats和innodb_index_stats和普通的表一样,也可以进行增删改查操作。修改之后需要使用flush table xx语句使优化器重新加载更改后的数据。
MySQL中的执行成本是由两方面组成的:
找出所有可能使用的索引,即possible keys
计算全表扫描的代价
成本=聚集索引页面数*1+表中记录数*0.2
**(这些信息保存在上一部分讲到的统计数据表中)**计算使用不同索引执行查询的代价
扫描区间数量(无论扫描区间占用了多少页面,都认为读取一个扫描区间的IO成本和读取一个页面的IO成本是相同的)
需要回表的记录数:页的头部中记录了该页当前有多少记录
找出最左的记录和最右的记录
如果两个记录像个小于十个页面,则可以精确的计算出记录数目
如果超过十个页面,则读取十个页面,计算页面平均包含多少记录,然后用平均值乘最左和最右记录之间的页面数量即可
成本=(区间数量+回表记录数)*1+二级索引记录数*0.2+聚集索引记录数*0.2
(二级索引记录数和聚集索引记录数其实就是回表记录数,计算的是读取和判断这条记录的开销)
对比,找出成本最低的代价
多次查询被驱动表的成本等于单次查询驱动表后的结果集数量乘以单次查询被驱动表的成本,也就是说驱动表的扇出或者被驱动表的查询成本越小,查询的开销越小。为了减小访问被驱动表的成本,应该尽量在被驱动表的连接列上面建立索引。
对于外连接而言,成本=单次访问驱动表的成本+驱动表扇出值(启发式预测) * 单词访问被驱动表的成本
对于内连接而言,因为驱动表和被驱动表的位置时可以互换的,所以需要计算不同表当驱动表时的成本,从而选择更优的一项
计算不同连接顺序时(如三表关联,则有6种连接顺序),有如下方式减小计算查询成本的性能消耗:
所以列中不重复的值的数量对于MySQL优化器十分重要,通过它可以计算在索引列中一个值平均重复多少行,它的应用场景主要有两个:
- 单表查询中的单点扫描区间太多:当in语句对应的单点扫描区间太多时,采用index dive的方式直接访问B+树索引来统计就记录的数量就太耗费性能了,所以直接依赖统计数据中一个值平均重复多少行来计算单点扫描区间对应的记录数量
- 在执行连接查询时,如果被驱动表的列有索引,则可以使用ref访问方法来查询被驱动表。而在优化器生成执行计划时,查询并没有真正执行,也就是对于select * from t1 join t2 on t1.col = t2.key where语句中,t1.col的值是不确定的,所以不能使用index dive的方式直接访问B+树索引来统计记录数量,只能依赖统计数据中一个值平均重复多少行来计算记录数量
如何对待null值有三种方式:
- 认为所有的null值都相等,如果某个索引列的null值很多,会使得查询哟花旗认为某个列中一个值的平均重复次数特别多,因此倾向于不适用索引查询
- 认为所有的null值都是不相等的
- 直接忽略null值
我们编写的查询语句的搜索条件本质上是表达式,有些表达式可能比较复杂无法高效执行,MySQL优化器会为我们简化这些表达式。
a=5 and b>a
化简为a=5 and b>5
MySQL查询优化器在基于成本和规则对一条查询语句进行优化后,会生成一个执行计划,这个执行计划展示了接下来执行查询的具体方式,比如多表连接的顺序是什么,采用什么方法来具体查询每个表等。我们可以通过EXPLAIN语句来查看某个查询语句的具体执行计划。
EXPLAIN语句会输出如下的列:
id:在一个大的查询语句中,每个select语句对应一个的id,如果采用联表查询虽然会产生多条记录,但是它们的id是相同的(出现在前面的为驱动表,出现在后面的为被驱动表)
select_type:select关键字对应的查询的类型
table:表名
partitions:匹配的分区信息。对于未分区的表,值为null
type:针对单表的访问方法
system:如果表中只有一条记录且统计数据是精确的(如MyISAM和MEMORY存储引擎),则使用system
const:通过主键或唯一二级索引与常数进行等值匹配
eq_ref:执行连接查询时,如果被驱动表是通过主键或者不允许为null的唯一二级索引列等值匹配的方式访问的
ref:通过普通的二级索引列与常量进行等值匹配
ref_or_null:当对普通二级索引列进行等值匹配时且该索引列的值也可以为null时
index_merge:一般情况下只会为单个索引生成扫描区间,在某些场景下可以使用Intersection、Union、Sort-Union这三种索引合并的方式来执行查询
range:使用索引获取某些单点扫描区间的记录或者获取某个或某些范围扫描区间的记录
index:使用可以使用索引覆盖,但是需要扫描全部的索引记录
all:全表扫描
possible_keys:可能用到的索引
key:实际使用的索引
key_len:实际使用的索引长度,其由以下三个部分组成
该列实际数据最多占用的存储空间,如int则为4字节,采用变长字符集utf8的varchar(100)则占用3*100=300
如果可以存储NULL值则长度再加一
如果是变长类型则长度再加二(用于存储实际占用空间的长度)
ref:当使用索引列等值查询时,与索引列进行等值匹配的对象的信息
rows:预估的需要读取的记录条数
filtered:针对预估的需要读取的记录,经过搜索条件过滤后剩余记录条数的百分比
Extra:一些额外的信息
no table used:没有from语句
impossible where:查询语句的where子句永远为false
no matching min/max row:当查询列表有min或者max聚集函数但是没有记录负荷where子句的搜索条件时
using index:使用覆盖索引执行查询时
using index condition:在存储引擎层执行搜索条件的判断(即索引条件下推)
对于查询语句
select * from s1 where Key1 > 'z' and key1 like '%a';
在没有索引条件下推特性之前,server层在生成执行计划后,是按照下面的步骤来执行这个查询的:
- server层调用存储引擎的接口定位到满足key1>'z’条件的第一条二级索引记录
- 存储引擎根据B+树索引快速定位到这条耳机索引记录后,根据该二级索引记录的主键值进行回表操作,将完整的用户记录返回给server层
- server层再判断其他的搜索条件是否成立,如果成立则将其发送给客户端,否则跳过该记录,向存储引擎要下一条记录
- 由于每条记录都有一个next_record的属性,根据该属性可以快速定位到符合key1>'z’条件的下一条二级索引记录,然后执行回表操作,将完整的记录返回给server层。之后重复步骤3,直到将索引的扫描区间(‘z’, +∞)内的所有记录都扫描完为止
在这个语句中虽然
key1 like '%z'
不能用于充当边界条件来减少要扫描的二级索引记录的数量,但是key1列是包含在查询的索引中的,那我们完全可以在查询到之后先判断是否满足key1 like '%z'
之后再进行回表,这就极大程度的省去了很多回表的操作。这就是索引条件下推,即将索引条件的判断下放到存储引擎来执行。
using where:在server层进行搜索条件的判断,一般出现在判断条件没有索引的情况
using filesort:无法使用索引(没有创建索引或需要大量数据进行回表成本更高的情况)进行排序时,则使用文件排序
using temporary:MySQL 需要创建临时表来存储查询的结果,常见于包含order by、group by和distinct等子句的查询
using join buffer (Block Nested Loop):连表查询的方式,表示当被驱动表不能有效地利用索引加快访问速度时,MySQL一般会分配一块连接缓冲区地内存块来加快查询速度, 将驱动表读出来放到join buffer中,再遍历被驱动表与驱动表进行查询
MySQL还提供了一种方式来查看某个执行计划花费的成本,只需要在explain和查询语句之间加上format=json
即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。