转自 http://blog.csdn.net/u012388497/article/details/25097159

本文通过一个案例来看看MySQL优化器如何选择索引和JOIN顺序。表结构和数据准备参考本文最后部分"测试环境"。这里主要介绍MySQL优化器的主要执行流程,而不是介绍一个优化器的各个组件(这是另一个话题)。

我们知道,MySQL优化器只有两个自由度:顺序选择;单表访问方式;这里将详细剖析下面的SQL,看看MySQL优化器如何做出每一步的选择。

explain
select *
fromemployee as A,department as B
whereA.LastName = 'zhou'and B.DepartmentID = A.DepartmentIDand 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成本评估。这里,等值表达式也是一个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(默认打开),不过至少需要有三个表以上的关联才会有"偷懒",所以本案例不适用。

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.9CREATE 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())'; donefor 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")'; doneshow 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())'; donefor 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 *
fromemployee as A,department as B
whereA.LastName = 'zhou'and B.DepartmentID = A.DepartmentIDand 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过滤之。

1 EXPLAIN 为什么ROWS检查的数据量不一样,
2 为什么不用快的索引,而必须使用FORCE INDEX的方式
3 同时出现如何处理 NAME LIKE ‘王’和‘王高’的区别

转载于:https://www.cnblogs.com/qcfeng/p/6952917.html

0606关于mysql优化原理相关推荐

  1. 一文说尽 MySQL 优化原理

    一文说尽 MySQL 优化原理 说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些 ...

  2. 我必须得告诉大家的MySQL优化原理

    说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用 SELECT*.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的工作原 ...

  3. 万字总结:学习MySQL优化原理,这一篇就够了!

    前言 说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型- 你是否真的理解这些优化技巧?是否理解其背后的工作原理 ...

  4. 转:万字总结:学习MySQL优化原理,这一篇就够了!

    前言 说起MySQL的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用NULL字段.合理创建索引.为字段选择合适的数据类型..... 你是否真的理解这些优化技巧?是否理解其背后的 ...

  5. MySQL 优化原理(三)

    聊聊 MySQL 配置. 大多数开发者可能不太会关注 MySQL 的配置,毕竟在基本配置没有问题的情况下,把更多的精力放在 schema 设计.索引优化和 SQL 优化上,是非常务实的策略.这时,如果 ...

  6. MySQL 优化原理(二)

    如果有同学看完上一篇关于 MySQL 文章,文末留有两个很开放的问题,如有兴趣可以在脑袋里想想.本文也会试着回答这两个问题,希望能给你一些参考.现在可以思考一个问题,如果数据量非常大的情况下,您根据业 ...

  7. MySQL 优化原理(一)

    说起 MySQL 的查询优化,相信大家收藏了一堆奇技淫巧:不能使用SELECT *.不使用 NULL 字段.合理创建索引.为字段选择合适的数据类型-- 你是否真的理解这些优化技巧?是否理解其背后的工作 ...

  8. mysql优化原理_【MySQL】我必须得告诉你们的MySQL优化原理3(下)INNODB配置

    INNODB:使用最广的存储引擎 innodb-buffer-pool-size 若是大部分是InnoDB表,那么InnoDB缓冲池或许比其余任何东西都更须要内存,InnoDB缓冲池缓冲的数据:索引. ...

  9. MySQL 优化原理

    说起 MySQL 的查询优化,相信大家收藏了一堆奇技淫巧:不能使用 SELECT *.不使用 NULL 字段.合理创建索引.为字段选择合适的数据类型- 你是否真的理解这些优化技巧?是否理解其背后的工作 ...

  10. MySQL 优化原理 (2)

    如果有同学看完上一篇关于 MySQL 文章,文末留有两个很开放的问题,如有兴趣可以在脑袋里想想.本文也会试着回答这两个问题,希望能给你一些参考.现在可以思考一个问题,如果数据量非常大的情况下,您根据业 ...

最新文章

  1. cocos2d-js动作的连续
  2. python matplotlib散点图-python-Matplotlib散点图图例
  3. equals()方法
  4. 前端学习(3051):vue+element今日头条管理-表格组件基本使用
  5. 上传文件块client实现
  6. swift学习之元组
  7. java中finaljava中this_Java中this,static,final,const用法详解
  8. 【ASP.Net MVC】AspNet Mvc一些总结
  9. Merry Christmas
  10. 二进制数除法 matlab,MATLAB求出不可约多项式(实现二进制加法、除法)
  11. EXCEL高级玩法之非常酷炫的动态数据分析报表
  12. 红亚太学链之区块链技术深度剖析第9章
  13. 了解世界杯赔率,让您运气更‘好‘(个人分享)
  14. leetcode每日一题第三十二天-剑指 Offer 65. 不用加减乘除做加法(easy??middle了吧)
  15. Javascript中删除数组中重复出现的元素
  16. 计算机应用基础0006 19春在线作业2,《计算机应用基础0006》19春在线作业 参考资料...
  17. Redis集群搭建(转自一菲聪天的“Windows下搭建Redis集群”)
  18. 专注与拓展-向携程学习
  19. 线程的 run() 和 start() 有什么区别?
  20. subprocess.Popen(执行命令)

热门文章

  1. EasyExcel实现excel导出【设置自定义样式--案例分析】
  2. MySQL基础学习第十三课(视图的创建)
  3. PR 音频去噪、音频信号增强、音频导出wav文件;
  4. 计算机网络安全需求包括哪些内容,什么是网络安全?网络安全包括哪几个方面?...
  5. 操作系统页表进程调度Tips
  6. 软件工程第二次作业——模仿实现主流网页
  7. 程序员求职全流程指南
  8. windows电脑桌面旋转快捷键
  9. 自己做网站有哪些方法呢?方法分享
  10. Java程序员培训班有用吗?是否专业