0104探究MySQL优化器对索引和JOIN顺序的选择
转自http://www.jb51.net/article/67007.htm,感谢博主
本文通过一个案例来看看MySQL优化器如何选择索引和JOIN顺序。表结构和数据准备参考本文最后部分"测试环境"。这里主要介绍MySQL优化器的主要执行流程,而不是介绍一个优化器的各个组件(这是另一个话题)。
我们知道,MySQL优化器只有两个自由度:顺序选择;单表访问方式;这里将详细剖析下面的SQL,看看MySQL优化器如何做出每一步的选择。
- explain
- select *
- from
- employee as A,department as B
- where
- A.LastName = 'zhou'
- and B.DepartmentID = A.DepartmentID
- and B.DepartmentName = 'TBX';
1. 可能的选择
这里看到JOIN的顺序可以是A|B或者B|A,单表访问方式也有多种,对于A表可以选择:全表扫描和索引`IND_L_D`(A.LastName = 'zhou')或者`IND_DID`(B.DepartmentID = A.DepartmentID)。对于B也有三个选择:全表扫描、索引IND_D、IND_DN。
2. MySQL优化器如何做
2.1 概述
MySQL优化器主要工作包括以下几部分:Query Rewrite(包括Outer Join转换等)、const table detection、range analysis、JOIN optimization(顺序和访问方式选择)、plan refinement。这个案例从range analysis开始。
2.2 range analysis
这部分包括所有Range和index merge成本评估(参考1 参考2)。这里,等值表达式也是一个range,所以这里会评估其成本,计算出found records(表示对应的等值表达式,大概会选择出多少条记录)。
本案例中,range analysis会针对A表的条件A.LastName = 'zhou'和B表的B.DepartmentName = 'TBX'分别做分析。其中:
表A A.LastName = 'zhou' found records: 51
表B B.DepartmentName = 'TBX' found records: 1
这两个条件都不是range,但是这里计算的值仍然会存储,在后面的ref访问方式评估的时候使用。这里的值是根据records_in_range接口返回,而对于InnoDB每次调用这个函数都会进行一次索引页的采样,这是一个很消耗性能的操作,对于很多其他的关系数据库是使用"直方图"的统计数据来避免这次操作(相信MariaDB后续版本也将实现直方图统计信息)。
2.3 顺序和访问方式的选择:穷举
MySQL通过枚举所有的left-deep树(也可以说所有的left-deep树就是整个MySQL优化器的搜索空间),来找到最优的执行顺序和访问方式。
2.3.1 排序
优化器先根据found records对所有表进行一个排序,记录少的放前面。所以,这里顺序是B、A。
2.3.2 greedy search
当表的数量较少(少于search_depth,默认是63)的时候,这里直接蜕化为一个穷举搜索,优化器将穷举所有的left-deep树找到最优的执行计划。另外,优化器为了减少因为搜索空间庞大带来巨大的穷举消耗,所以使用了一个"偷懒"的参数prune_level(默认打开),具体如何"偷懒",可以参考JOIN顺序选择的复杂度。不过至少需要有三个表以上的关联才会有"偷懒",所以本案例不适用。
2.3.3 穷举
JOIN的第一个表可以是:A或者B;如果第一个表选择了A,第二个表可以选择B;如果第一个表选择了B,第二个表可以选择A;
因为前面的排序,B表的found records更少,所以JOIN顺序穷举时的第一个表先选择B(这个是有讲究的)。
(*) 选择第一个JOIN的表为B
(**) 确定B表的访问方式
因为B表为第一个表,所以无法使用索引IND_D(B.DepartmentID = A.DepartmentID),而只能使用IND_DN(B.DepartmentName = 'TBX')
使用IND_DN索引的成本计算:1.2;其中IO成本为1。
是否使用全表扫描:这里会比较使用索引的IO成本和全表扫描的IO成本,前者为1,后者为2;所以忽略全表扫描
所以,B表的访问方式ref,使用索引IND_D
(**) 从剩余的表中穷举选出第二个JOIN的表,这里剩余的表为:A
(**) 将A表加入JOIN,并确定其访问方式
可以使用的索引为:`IND_L_D`(A.LastName = 'zhou')或者`IND_DID`(B.DepartmentID = A.DepartmentID)
依次计算使用索引IND_L_D、IND_DID的成本:
(***) IND_L_D A.LastName = 'zhou'
在range analysis阶段给出了A.LastName = 'zhou'对应的记录约为:51。
所以,计算IO成本为:51;ref做IO成本计算时会做一次修正,将其修正为worst_seek(参考)
修正后IO成本为:15,总成本为:25.2
(***) IND_DID B.DepartmentID = A.DepartmentID
这是一个需要知道前面表的结果,才能计算的成本。所以range analysis是无法分析的
这里,我们看到前面表为B,found_record是1,所以A.DepartmentID只需要对应一条记录就可以了
因为具体取值不知道,也没有直方图,所以只能简单依据索引统计信息来计算:
索引IND_DID的列A.DepartmentID的Cardinality为1349,全表记录数为1349
所以,每一个值对应一条记录,而前面表B只有一条记录,所以这里的found_record计算为1*1 = 1
所以IO成本为:1,总成本为1.2
(***) IND_L_D成本为25.2;IND_DID成本为1.2,所以选择后者为当前表的访问方式
(**) 确定A使用索引IND_DID,访问方式为ref
(**) JOIN顺序B|A,总成本为:1.2+1.2 = 2.4
(*) 选择第一个JOIN的表为A
(**) 确定A表的访问方式
因为A表是第一个表,所以无法使用索引`IND_DID`(B.DepartmentID = A.DepartmentID)
那么只能使用索引`IND_L_D`(A.LastName = 'zhou')
使用IND_L_D索引的成本计算,总成本为25.2;参考前面计算;
(**) 这里访问A表的成本已经是25.2,比之前的最优成本2.4要大,忽略该顺序
所以,这次穷举搜索到此结束
把上面的过程简化如下:
(*) 选择第一个JOIN的表为B
(**) 确定B表的访问方式
(**) 从剩余的表中穷举选出第二个JOIN的表,这里剩余的表为:A
(**) 将A表加入JOIN,并确定其访问方式
(***) IND_L_D A.LastName = 'zhou'
(***) IND_DID B.DepartmentID = A.DepartmentID
(***) IND_L_D成本为25.2;IND_DID成本为1.2,所以选择后者为当前表的访问方式
(**) 确定A使用索引IND_DID,访问方式为ref
(**) JOIN顺序B|A,总成本为:1.2+1.2 = 2.4
(*) 选择第一个JOIN的表为A
(**) 确定A表的访问方式
(**) 这里访问A表的成本已经是25.2,比之前的最优成本2.4要大,忽略该顺序
至此,MySQL优化器就确定了所有表的最佳JOIN顺序和访问方式。
3. 测试环境
- MySQL: 5.1.48-debug-log innodb plugin 1.0.9
- CREATE TABLE `department` (
- `DepartmentID` int(11) DEFAULT NULL,
- `DepartmentName` varchar(20) DEFAULT NULL,
- KEY `IND_D` (`DepartmentID`),
- KEY `IND_DN` (`DepartmentName`)
- ) ENGINE=InnoDB DEFAULT CHARSET=gbk;
- CREATE TABLE `employee` (
- `LastName` varchar(20) DEFAULT NULL,
- `DepartmentID` int(11) DEFAULT NULL,
- KEY `IND_L_D` (`LastName`),
- KEY `IND_DID` (`DepartmentID`)
- ) ENGINE=InnoDB DEFAULT CHARSET=gbk;
- for i in `seq 1 1000` ; do mysql -vvv -uroot test -e 'insert into department values (600000*rand(),repeat(char(65+rand()*58),rand()*20))'; done
- for i in `seq 1 1000` ; do mysql -vvv -uroot test -e 'insert into employee values (repeat(char(65+rand()*58),rand()*20),600000*rand())'; done
- for i in `seq 1 50` ; do mysql -vvv -uroot test -e 'insert into employee values ("zhou",27760)'; done
- for i in `seq 1 200` ; do mysql -vvv -uroot test -e 'insert into employee values (repeat(char(65+rand()*58),rand()*20),27760)'; done
- for i in `seq 1 1` ; do mysql -vvv -uroot test -e 'insert into department values (27760,"TBX")'; done
- show index from employee;
- +----------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
- | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
- +----------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
- | employee | 1 | IND_L_D | 1 | LastName | A | 1349 | NULL | NULL | YES | BTREE | |
- | employee | 1 | IND_DID | 1 | DepartmentID | A | 1349 | NULL | NULL | YES | BTREE | |
- +----------+------------+----------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+
- show index from department;
- +------------+------------+----------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+
- | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
- +------------+------------+----------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+
- | department | 1 | IND_D | 1 | DepartmentID | A | 1001 | NULL | NULL | YES | BTREE | |
- | department | 1 | IND_DN | 1 | DepartmentName | A | 1001 | NULL | NULL | YES | BTREE | |
- +------------+------------+----------+--------------+----------------+-----------+-------------+----------+--------+------+------------+---------+
4. 构造一个Bad case
因为关联条件中MySQL使用索引统计信息做成本预估,所以数据分布不均匀的时候,就容易做出错误的判断。简单的我们构造下面的案例:
表和索引结构不变,按照下面的方式构造数据:
- for i in `seq 1 10000` ; do mysql -uroot test -e 'insert into department values (600000*rand(),repeat(char(65+rand()*58),rand()*20))'; done
- for i in `seq 1 10000` ; do mysql -uroot test -e 'insert into employee values (repeat(char(65+rand()*58),rand()*20),600000*rand())'; done
- for i in `seq 1 1` ; do mysql -uroot test -e 'insert into employee values ("zhou",27760)'; done
- for i in `seq 1 10` ; do mysql -uroot test -e 'insert into department values (27760,"TBX")'; done
- for i in `seq 1 1000` ; do mysql -uroot test -e 'insert into department values (27760,repeat(char(65+rand()*58),rand()*20))';
- done
- explain
- select *
- from
- employee as A,department as B
- where
- A.LastName = 'zhou'
- and B.DepartmentID = A.DepartmentID
- and B.DepartmentName = 'TBX';
- +----+-------------+-------+------+-----------------+---------+---------+---------------------+------+-------------+
- | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
- +----+-------------+-------+------+-----------------+---------+---------+---------------------+------+-------------+
- | 1 | SIMPLE | A | ref | IND_L_D,IND_DID | IND_L_D | 43 | const | 1 | Using where |
- | 1 | SIMPLE | B | ref | IND_D,IND_DN | IND_D | 5 | test.A.DepartmentID | 1 | Using where |
- +----+-------------+-------+------+-----------------+---------+---------+---------------------+------+-------------+
可以看到这里,MySQL执行计划对表department使用了索引IND_D,那么A表命中一条记录为(zhou,27760);根据B.DepartmentID=27760将返回1010条记录,然后根据条件DepartmentName = 'TBX'进行过滤。
这里可以看到如果B表选择索引IND_DN,效果要更好,因为DepartmentName = 'TBX'仅仅返回10条记录,再根据条件A.DepartmentID=B.DepartmentID过滤之。
相关链接http://www.cnblogs.com/hellohell/p/5718238.html
0104探究MySQL优化器对索引和JOIN顺序的选择的更多相关文章
- 机智的MySQL优化器 --- is null
[介绍] 工作的越久越到的的问题越多,就越是觉得一些“老话”历久弥新:由于最近的学习计划是深入的学习一遍MySQL优化器:学习过程中的一些成果 也会发布到这里,一来是为了整理自己已经知道的和新学到的, ...
- mysql 优化实例之索引创建
mysql 优化实例之索引创建 优化前: pt-query-degist分析结果: # Query 23: 0.00 QPS, 0.00x concurrency, ID 0x78761E301CC7 ...
- 《Mysql - 优化器是如何选择索引的?》
一:概念 - 在 索引建立之后,一条语句可能会命中多个索引,这时,索引的选择,就会交由 优化器 来选择合适的索引. - 优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句. 二: ...
- MySQL优化器不使用索引的情况
优化器选择不适用索引的情况 有时候,有乎其并没有选择索引而去查找数据,而是通过扫描聚集索引,也就是直接进行全表的扫描来得到数据.这种情况多发生于范围查找.JOIN链接操作等情况.例如 ; 通过SHOW ...
- 数据库 mysql 优化器原理
MySQL查询优化器有几个目标,但是其中最主要的目标是尽可能地使用索引,并且使用最严格的索引来消除尽可能多的数据行. 你的最终目标是提交SELECT语句查找数据行,而不是排除数据行.优化器试图排除数据 ...
- MySQL优化器cost计算
记录MySQL 5.5上,优化器进行cost计算的方法. 第一篇: 单表的cost计算 数据结构: 1. table_share: 包含了表的元数据,其中索引部分: key_info:一个key的结构 ...
- MySQL优化器 --- index_merge
[背景] 对于关系数据库中的一张表,通常来说数据页面的总大小要比较某一个索引占用的页面要大的多(上面说的索引是不包涵主键索引的); 更进一步我们可以推导出,如果我们通过读索引就能解决问题,那么它相比读 ...
- MySQL优化器功能开关optimizer_switch
MySQL 8.0新增特性 use_invisible_indexes:是否使用不可见索引,MySQL 8.0新增可以创建invisible索引,这一开关控制优化器是否使用invisible索引,on ...
- 如何干涉MySQL优化器使用hash join
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 前言 实验 总结 前言 数据库的优化器相当于人类的大 ...
随机推荐
- VMware安装CentOS时,无法以图形界面安装解决办法
有的同学问: 用虚拟机软件(vmware.VirtualBox)安装CentOS系统时, 安装过程中没有中文,也没有出现图形界面,都是以命令行方式去安装, 有时候又会出现图形界面,不知道哪里配置的问题 ...
- node-sass报错解决方法
在Vue.js中,每一个vue文件都是一个组件,在.vue文件中可以将模板,脚本,样式写在一起,便于组织整个组件.在使用template,script时,编写css样式时,都进行的特别顺利,唯独当我想 ...
- ES6之解构赋值
截止到ES6,共有6种声明变量的方法,分别是var .function以及新增的let.const.import和class: 我们通常的赋值方法是: var foo='foo'; function ...
- Using Dagger2 in Android
Dagger2是一个Java和Android的依赖注入框架. 本文介绍Android中dagger2的基本使用. 其中包括@Inject, @Component, @Module和@Provides注 ...
- 蓝牙协议中的SBC编码
一.从信息的传输说起  上图是一个典型的蓝牙耳机应用场景.手机上的音频信息经过编码以后通过蓝牙协议被蓝牙耳机接收,经过解码以后,蓝牙耳机成功获取手机上的音频信息,然后再转化为振动被人耳识别.这是一个 ...
- Linux2.6内核协议栈系列--TCP协议2.接收
1.排队机制 接收输入TCP报文时,有三个队列: ● 待处理队列 ● 预排队队列 ● 接收队列 接收队列包含了处理过的TCP数据段,也就是说,去除了全部的协议头,正准备将数据复制到用户应用程序.接收队 ...
- 一些Titanium学习的地方
利用titanium兑现外汇兑换计算的ios代码 http://rensanning.iteye.com/blog/1325011 Titanium兑现相关的报表功能 http://www.s ...
- MVC View中获取action、controller、area名称
获取控制器名称: ViewContext.RouteData.Values["controller"].ToString(); 获取Action名称: ViewContext.Ro ...
- 【设计模式】GoF设计模式学习总结
什么是设计模式 为解决某一类普遍存在的问题而提供的一种解决方案: 分类及定义 创建型模式(5) 1.原型模式:通过复制一个已存在对象来生成一个新对象,被复制的对象称为原型:详情... 2.单例模式:一 ...
- 利用Sharding-Jdbc实现分表
你们团队使用SpringMVC+Spring+JPA框架,快速开发了一个NB的系统,上线后客户订单跟雪花一样纷沓而来. 慢慢地,你的心情开始变差,因为客户和产品的抱怨越来越频繁,抱怨的最多的一个问题就 ...