关于Join
如上图所示,left join和inner join的取值范围还是有很明显的不同的,看网上不少文章优化left join也不交代上下文环境,直接就是教唆把left join换成inner join,只能说是章口就莱,文体两开花...
关于笛卡尔积
简言之就是强拼,不带条件的硬拼2(多)张表,结果就是直接做乘法
SELECT * FROM a CROSS JOIN b;
SELECT * FROM a INNER JOIN b;
SELECT * FROM a,b;
SELECT * FROM a NATURE JOIN b;
SELECT * FROM a NATURA join b;
如何判断驱动表
如果没有where条件
左连接,如果没有where条件,则左表为驱动表
右连接,如果没有where条件,右表为驱动表
如果有where条件
(参照《数据库索引设计与优化》第8章内容对外表的判定应该是下面写的这个样子)
(参考读书笔记:索引读书笔记)
但是实际操作中有本地谓词的表并不一定百分百的是外表,而是由优化器来判断的
#个人实际优化过程中用的最多的还是下面这条
explain规则:explain语句结果中,第一行为驱动表(该定律适用于join;子查询的话要分情况)
性能优化相关
①选谁做驱动表
引用一个举烂了的例子,在没有过滤条件的情况下,外表有多少行就会被加载多少次;参考《索引设计与优化》这本书的说法,每次加载相当于一次随机读;
假设A表10000行,B表100行;也就是说如果用A表做驱动表会伴随着10000次随机读,而如果是B表作为驱动表,会伴随着100次随机读。
所以普遍的优化方案是小表作为外表(驱动表),用小表去驱动大表。
②创建合理的索引
选择小表驱动大表,然后在内表(被驱动的大表)的连接谓词上建立索引,使得对内表的查询走索引提高效率。这种做法最高效的就是外表是一张小表,而内表的连接谓词刚好是内表的主键。
在对这种没有查询过滤条件的语句执行explain的时候会非常不爽,因为驱动表通常是走的全表扫描;在我的测试中,如果外表上没有查询过滤条件(也就是本地谓词)的话,无论查询项是否是索引项,都是走全表。(换句话说,也就是在没有过滤条件的情况下,外表其实有没有索引对于当前语句的优化意义都不太明显(因为会走全表嘛),如果专门因为某个语句去对外表建一大堆索引的可以省省了)
#用下面的例子来解释上面2句话
m表和t表都是25W行左右,大小差距不大;根据开篇说的驱动表确立规则,无查询过滤条件的左连接,左表为驱动表;蓝框表明了这一点。
绿框的t.charge_no是t表的连接谓词,在这一列上建有一个选择性不错的索引,因此在explain的第二行可以看到内表的查询走了索引,type为ref,效率还算不错。(如果这个连接谓词是t表的主键,效率会更高)
红框处可以看到,对m表取的查询值,我们仅仅取了m.charge_no这一列,而且m.charge_no上是建有索引的,但explain的第一行粉框的ALL表明m表的查询仍然走了全表(即使是我们m表的查询列只取了建有索引的列)。
#如何让外表走索引?
方法:对外表增加过滤条件(本地谓词),并且在本地谓词上创建索引
查询过滤条件中只有t表的本地谓词,因此无论左连接还是右连接,t表都是驱动表
由于过滤条件t.partner_id和t.trade_date上建有索引,因此外表t不再走全表扫描
注意2处蓝框是有关联的,右下角的那个大蓝框里面是没有temporary的,这与接下来讨论的有关
③合理的排序
前面2张图里面的语句是我在项目中遇到的一个慢查询,本体并非前面2张图贴出的那样,区别在于order by后面跟的字段不同,原语句order by后面跟着的是一个内表字段
多表join执行时,优化器内部会先找出外表,之后会对外表进行排序;如果order by后面跟的是外表字段,则排序会在这时完成。但如果order by后面的字段是内表字段,则在所有结果集选出之后,再进行一次排序,这也就是执行计划里面Using temporary的由来了。
项目中的原语句一来没有本地谓词,二来还用内表进行排序,导致最后的执行时间达到8s之久;与研发沟通之后将语句改成用外表字段排序,执行时间所短到0.3s。
④强行指定驱动表
左右连接都可以指定驱动表,但(inner) join无法强行指定,通常来说都是通过系统自动去判定
强行指定方式:straight_join
这个参数会强行指定左表是外表
例子就截个别人的图吧
多表join优化案例
eg.
红框处是可能需要加索引的各种连接谓词和本地谓词,蓝框标示了执行顺序扫描方式等帮助信息
#对执行计划进行分析
在执行计划的第5行可以发现pcb表走了全表扫描,扫描行数达到95W行;通过执行计划的第4行可以发现这里有一个派生表,这个派生表是由pcb和ppv做inner join形成的,这里很明显pcb成了这个连接查询的外表,查询ppv表,发现ppv表仅仅只有19行。这是典型的大表驱动小表
查询pcb表的索引,发现pcb表的连接谓词和本地谓词上面都没有建索引!而ppv.id是ppv表的主键(mysql优化器大概就是因为这个原因让ppv表做了内表吧...)
把pcb表的连接谓词和本地谓词做了一个联合索引之后,扫描行数从95W缩减至5.4W,优化器很识趣的将ppv选做外表了
消灭了95W行的扫描之后,盘面上最刺眼的就是第二行那个2871了,对pc表的索引进行查询发现pc.client_no列上也没有索引,由于pc.client_no列是一个本地谓词,在添加索引之后,pc表直接跳到了explain的第一行。优化后的执行计划如下图
对比pp表和pc表,pp表有16行数据,pc表有3000行数据,都不算太大,无论谁作为驱动表都不会有太大的性能提升了,优化也到此结束;执行时间由1s缩减到0.29s
参考文章
2.《索引设计与优化》