SELECT

*

FROM

tblA,

tblB,

tblC

WHERE

tblA.col1 = tblB.col1

AND tblA.col2 = tblC.col1;

explain的结果如下:

Java代码

+-------+------+---------------+------+---------+------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+------+---------+------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

| tblC | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

+-------+------+---------------+------+---------+------+------+-------------+

+-------+------+---------------+------+---------+------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+------+---------+------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

| tblC | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

+-------+------+---------------+------+---------+------+------+-------------+

最后,在MySQL的手册中(7.2.1):

表以它们在处理查询过程中将被MySQL读入的顺序被列出。MySQL用一遍扫描多次联接(single-sweep multi-join)的方式解决所有联接。这意味着MySQL从第一个表中读一行,然后找到在第二个表中的一个匹配行,然后在第3个表中等等。当所有的表处理完后,它输出选中的列并且返回表清单直到找到一个有更多的匹配行的表。从该表读入下一行并继续处理下一个表。

如手册所说的,MySQL读第一个表(tnlA),然后第二个(tblB),然后第三个(tblC),像explain中输出的一样。先前的表中的值用来查找当前表中的行。在我们的例子中,tblA中的值用来找tblB中的匹配行,然后tblB的值来找tblC的行。当一个完整的扫描结束(在表tblA,tblB,tblC中找到了结果),MySQL不会返回tblA,它到tblB中查看是否有更多的行匹配当前tblA的值。如果有,它拿出这一行,然后再在tblC中找匹配的。记住 MySQL连接的基本原则是很重要的:先前的表中的值用来查找当前表中的行。

按原理建索引

知道了MySQL使用从tblA中得到的值查找tblB中的行,我们需要怎么建索引来帮助MySQL?为此我们要知道它需要什么。考虑连接tblA和 tblB:它们通过“tblA.col1 = tblB.col1”来连接。我们已经有了tblA.col1的值,所以MySQL需要一个tblB.col1的值来完成等值操作。因此如果MySQL需要tblB.col1,我们就在tblB.col1上加索引。加了之后,这是新的explain结果:

Java代码

+-------+------+---------------+----------+---------+-----------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+----------+---------+-----------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ref | ndx_col1 | ndx_col1 | 5 | tblA.col1 | 1 | Using where |

| tblC | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

+-------+------+---------------+----------+---------+-----------+------+-------------+

+-------+------+---------------+----------+---------+-----------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+----------+---------+-----------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ref | ndx_col1 | ndx_col1 | 5 | tblA.col1 | 1 | Using where |

| tblC | ALL | NULL | NULL | NULL | NULL | 1000 | Using where |

+-------+------+---------------+----------+---------+-----------+------+-------------+

如上,MySQL现在使用ndx_col1索引来连接tblB到tblA。就是说,当MySQL要找tblB中的行时,使用了ndx_col1索引通过 tblA.col1的值直接得到匹配的行,而不是像以前需要做表扫描。这就是为什么tblB的ref列说“tablA.col1”。tblC现在还是用表扫描,这可以通过同样的方法解决。查看MySQL的需求:从sql中连接两表的语句“tblA.col2 = tblC.col1”可以看出它需要tblC.col1因为我们已经有了tblA.col2。给这一列加上索引之后explain:

Java代码

+-------+------+---------------+----------+---------+-----------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+----------+---------+-----------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ref | ndx_col1 | ndx_col1 | 5 | tblA.col1 | 1 | Using where |

| tblC | ref | ndx_col1 | ndx_col1 | 5 | tblA.col2 | 1 | Using where |

+-------+------+---------------+----------+---------+-----------+------+-------------+

+-------+------+---------------+----------+---------+-----------+------+-------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+------+---------------+----------+---------+-----------+------+-------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1000 | |

| tblB | ref | ndx_col1 | ndx_col1 | 5 | tblA.col1 | 1 | Using where |

| tblC | ref | ndx_col1 | ndx_col1 | 5 | tblA.col2 | 1 | Using where |

+-------+------+---------------+----------+---------+-----------+------+-------------+

更复杂的查询

在实际中不会遇到刚才那种sql。所以你可能更想看看这样的:

SELECT

COUNT(tblB.a_id) as correct,

tblA.type,

tblA.se_type

FROM

tblA,

tblB,

tblC,

tblD

WHERE

tblA.ex_id = tblC.ex_id

AND tblC.st_ex_id = tblB.st_ex_id

AND tblB.q_num = tblA.q_num

AND tblB.se_num = tblA.se_num

AND tblD.ex_id = tblA.ex_id

AND tblD.exp <> tblB.se_num

AND tblB.ans = tblA.ans

AND tblA.ex_id = 1001

AND tblC.r_id = 542

GROUP BY

tblA.type,

tblA.se_type;

乍一看是很复杂的:有4个表,有聚合函数,有9个where条件,还有一个group by。explain的伟大之处在于我们现在可以忽略这些,每次只看两个表,判断每一步MySQL需要什么。这是一个实际的查询,只是字段名有一些改动。explain的结果:

Java代码

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1080 | Using where; Using temporary; Using filesort |

| tblB | ALL | NULL | NULL | NULL | NULL | 87189 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1080 | Using where; Using temporary; Using filesort |

| tblB | ALL | NULL | NULL | NULL | NULL | 87189 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+---------+---------+---------------+-------+----------------------------------------------+

判断连接影响的主要看结果集。结果集就是查询的结果。对于连接,一个估计结果集大小的方法是把MySQL预测的读取每个表的行数相乘。作为估计,这样做比较偏向于坏的情况,因为where条件通常会减少很多的行数。但这个查询的结果集有9400万行。这就是没有索引连接很危险的原因;几千行乘几千行你就会有一个上百万的结果集了。

那么现在这个查询需要什么?从tblA和tblB开始。在sql中:

AND tblB.q_num = tblA.q_num

AND tblB.se_num = tblA.se_num

AND tblB.ans = tblA.ans

MySQL 至少需要q_num, se_num, ans中的一个。我选择在se_num和q_num上加索引因为在几乎所有其他的查询中我都会需要它们。折中是优化的一部分,多数人没有时间去为每一个查询找最优的索引方案,只能是找到一个对于大多数情况而言最优的方案。在tblB上加索引(se_num, q_num),explain的结果:

Java代码

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1080 | Using where; Using temporary; Using filesort |

| tblB | ref | ndx_secn_qn | ndx_secn_qn | 2 | tblA.se_num,tblA.q_num | 641 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| tblA | ALL | NULL | NULL | NULL | NULL | 1080 | Using where; Using temporary; Using filesort |

| tblB | ref | ndx_secn_qn | ndx_secn_qn | 2 | tblA.se_num,tblA.q_num | 641 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

现在结果集下降了99.3%变为692280行。但为什么要停在这里?我们可以很容易的解决tblA的表扫描。因为它是第一个表,我们并不需要为连接加索引,这在tblB上已经做过了。一般来说,给第一个表加索引可以把它当成只在这一个表上查询的情况。在这个例子中很幸运,tblA是:"AND tblA.ex_id = 1001"。我们只需要加ex_id索引:

Java代码

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| tblA | ref | ndx_ex_id | ndx_ex_id | 4 | const | 1 | Using where; Using temporary; Using filesort |

| tblB | ref | ndx_secn_qn | ndx_secn_qn | 2 | tblA.se_num,tblA.q_num | 641 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| table | type | possible_keys | key | key_len | ref | rows | Extra |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

| tblA | ref | ndx_ex_id | ndx_ex_id | 4 | const | 1 | Using where; Using temporary; Using filesort |

| tblB | ref | ndx_secn_qn | ndx_secn_qn | 2 | tblA.se_num,tblA.q_num | 641 | Using where |

| tblC | eq_ref | PRIMARY | PRIMARY | 4 | tblB.st_ex_id | 1 | Using where |

| tblD | eq_ref | PRIMARY | PRIMARY | 4 | tblA.ex_id | 1 | Using where |

+-------+--------+---------------+-------------+---------+------------------------+------+----------------------------------------------+

现在结果集是641行。相比开始的9400万,可以说了下降了100%。如果继续研究这个查询我们还可以去掉temp table和filesort,但现在查询已经很快了,也已经说明了如何为连接加索引。尽管最初看这个查询很麻烦,但可以看到只要每次独立的看两张表,为 MySQL的需求加索引,整个过程并不困难。

结论

为复杂的连接加索引要认识到两件事:

1. 不管sql多复杂,每次只看explain中的两个表

2. 先前表中的值已经有了,我们的工作就是通过索引帮助MySQL在当前表中使用这些值来找到匹配行

分享到:

2008-08-14 23:25

浏览 929

评论

mysql pt工具 加索引_[转]MySQL中如何为连接添加索引相关推荐

  1. 在mysql中如何为连接添加索引_在MySQL中如何为连接添加索引

    http://hackmysql.com/case4 译文: 我先通过一个简单的例子说明在MySQL中如何为连接添加索引,然后再看一个有挑战性的例子. 简单的3个表的连接 表结构很简单,3个表tblA ...

  2. MySQL pt工具应用

    MySQL pt工具的应用 1.pt工具安装 [root@master ~]# yum install -y percona-toolkit-3.1.0-2.el7.x86_64.rpm2.常用工具使 ...

  3. mysql fifo 批量加载_使用pt-fifo-split 工具往mysql插入海量数据

    在<mysql插入/更新数据>这篇文章提到,使用LOAD DATA INFILE语句,可以从一个文件直接加载数据到mysql中,但如果文件非常大,可能还需要对文件进行切割,分多次加载,这种 ...

  4. mysql为什么用b加树_为什么MySQL要用B+树?聊聊B+树与硬盘的前世今生【宇哥带你玩转MySQL 索引篇(二)】...

    为什么MySQL要用B+树?聊聊B+树与硬盘的前世今生 在上一节,我们聊到数据库为了让我们的查询加速,通过索引方式对数据进行冗余并排序,这样我们在使用时就可以在排好序的数据里进行快速的二分查找,使得查 ...

  5. mysql1000w数据怎么加索引_给mysql一百万条数据的表添加索引

    直接alter table add index 添加索引,执行一个小时没反应,并且会导致锁表:故放弃该办法,最终解决办法如下: 一.打开mysql 命令行客户端 这里我们那可以看到导出的数据文件所存放 ...

  6. 查询没有走索引_关于MySQL种的in函数到底走不走索引、我和同事差点大打出手!...

    " 我是小羊同学,一个兢兢业业的程序员" 背景:有一天同事突然问我为什么加了in查询就突然变慢了.小羊脱口而出:"in不走索引!" 于是就炸开了锅:in不走索引 ...

  7. mysql in 索引_关于MySQL种的in函数到底走不走索引、我和同事差点大打出手!

    " 我是小羊同学,一个兢兢业业的程序员" 背景:有一天同事突然问我为什么加了in查询就突然变慢了.小羊脱口而出:"in不走索引!" 于是就炸开了锅:in不走索引 ...

  8. mysql distinct多个字段_深入浅出Mysql索引的那些事儿

    一.索引的作用 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重. 在数据 ...

  9. 数据库mysql建立索引_为mysql数据库建立索引

    前些时候,一位颇高级的程序员居然问我什么叫做索引,令我感到十分的惊奇,我想这绝不会是沧海一粟,因为有成千上万的开发者(可能大部分是使用MySQL的)都没有受过有关数据库的正规培训,尽管他们都为客户做过 ...

最新文章

  1. 键盘回车事件导致页面刷新的问题
  2. vlan网络下的设置
  3. 《计算机组成原理》课程设计任务书——TEC-2实验系统——微程序设计
  4. 【Tools】Win10系统搭建匿名FTP服务器详解
  5. C++ Prime:sizeof运算符
  6. ADO.NET、ODP.NET、Linq to SQL、ADO.NET Entity 、NHibernate在Oracle下的性能比较
  7. XML学习笔记--导航
  8. c语言统计输入的字符数字的个数字,请问这个用c怎么做:输入一串字符,分别统计其中数字和字母的个数...
  9. 鼠标hover表格头部信息出现闪烁
  10. $ajax 获取返回值object,来自.ajax()调用的数据的jQuery .find()返回“ [object Object]”,而不是di...
  11. 架构:一张电商架构的大图
  12. Python使用openpyxl插入excel批注,修改批注
  13. Echarts 如何实现一张图现切换不同的X轴
  14. windows server 2016 由于没有远程桌面授权服务器可以提供许可证,远程会话被中断。请跟服务器管理员联系。
  15. 解决xhtmlrenderer flying-saucer-pdf-itext5 生成pdf时html中table分页内容太多挤到第二页问题。
  16. 花青素类荧光染料Sulfo-Cy3.5 NH2,Sulfo-Cyanine3.5 amine,磺酸基-花青素Cyanine3.5 氨基,可以用来标记蛋白
  17. 电脑开机不能进入系统--死机
  18. inode 耗尽故障处理办法
  19. FineUI大版本升级,外置ExtJS库、去AXD化、表格合计行、表格可编辑单元格的增删改、顶......
  20. [嘿就这么样吧 谁看谁的脸色]井冈春天牌演.活力果子

热门文章

  1. php获得客户端ip地址范例
  2. [转载]LFSR的工作原理以及LFSR在CRC上的应用
  3. 简单的电商分销管理系统介绍
  4. Bitmap 贴图加工成 PBR 贴图
  5. 数据库原理题型 - 综合应用题
  6. JPA @PersistenceContext及@Transactional Annotation
  7. pb 修改数据窗口种指定字段位置_在PB中控制 数据窗口 列修改属性.doc
  8. 哈夫曼树+K叉哈夫曼树
  9. js判断数组,对象,是否为空,是否含有某个值,判断数组中对象是否有某个值
  10. matlab中使用simulink标准化输出图片