正文
Inside君发现不多有人可以完成讲明白My
SQL的Join类型与
算法,网上流传着的要提高Join性能,加大变量join_buffer_size的谬论更是随处可见。固然,也有一些无知的PGer攻击MySQL不支持Hash Join,因此不适合一些分析类的操做。MySQL的确不支持Hash Join,也不支持Sort Merge Join,可是MySQL在Join上也有本身的独特的优化与处理,此外,分支版本MariaDB已支持Hash Join,所以拿MySQL来作一些“简单”的分析查询也是彻底可以接受的。固然,若是数据量真的上去了,那么即便支持Hash Join的传统MPP架构的关系型数据库可能也是不合适的,这类分析查询或许应该交给更为专业的
Hadoop集群来计算。
Join的成本
在讲述MySQL的Join类型与算法前,看看两张表的Join的过程:
上图的Fetch阶段是指当内表关联的列是辅助索引时,可是须要访问表中的数据,那么这时就须要再访问主键索引才能获得数据的过程,不论表的存储引擎是InnoDB存储引擎仍是MyISAM,这都是没法避免的,只是MyISAM的回表速度要快点,由于其辅助索引存放的就是指向记录的指针,而InnoDB存储引擎是索引组织表,须要再次经过索引查找才能定位数据。
Fetch阶段也不是必须存在的,若是是汇集索引连接,那么直接就能获得数据,无需回表,也就没有Fetch这个阶段。另外,上述给出了两张表之间的Join过程,多张表的Join就是继续上述这个过程。
接着计算两张表Join的成本,这里有下列几种概念:
外表的扫描次数,记为O。一般外表的扫描次数都是1,即Join时扫描一次驱动表的数据便可
内表的扫描次数,记为I。根据不一样Join算法,内表的扫描次数不一样
读取表的记录数,记为R。根据不一样Join算法,读取记录的数量可能不一样
Join的比较次数,记为M。根据不一样Join算法,比较次数不一样
回表的读取记录的数,记为F。若Join的是辅助索引,可能须要回表取得最终的数据
评判一个Join算法是否优劣,就是查看上述这些操做的开销是否比较小。固然,这还要考虑I/O的访问方式,顺序仍是随机,总之Join的调优也是门艺术,并不是想象的那么简单。
Simple Nested-Loop Join
网上大部分说MySQL只支持Nested-Loop Join,故性能差。可是Nested-Loop join必定差吗?Hash Join比Nested-Loop Join强?Inside君感受这样的理解都是片面的,Hash Join可能仅是Nested-Loop Join的一种变种。因此Inside君打算从算法的角度来分析MySQL支持的Join,并以此分析对于Join语句的优化。
首先来看Simple Nested-Loop Join(如下简称SNLJ),也就是最朴素的Nested-Loop Join,其算法伪代码以下所示:
For each row r in R do
Foreach row s in S do
If r and s satisfy the join condition
Then output the tuple
下图能更好地显示整个SNLJ的过程:
SNLJ的算法至关简单、直接。即外表(驱动表)中的每一条记录与内表中的记录进行判断。可是这个算法也是至关粗暴的,粗暴的缘由在于这个算法的开销其实很是大。假设外表的记录数为R,内表的记录数位S,根据上一节Inside君对于Join算法的评判标准来看,SNLJ的开销以下表所示:
能够看到读取记录数的成本和比较次数的成本都是S*R,也就是笛卡儿积。假设外表内表都是1万条记录,那么其读取的记录数量和Join的比较次数都须要上亿。这样的算法开销,Inside君也只能:呵呵。
Index Nested-Loop Join
SNLJ算法虽然简单明了,可是也是至关的粗暴。所以,在Join的优化时候,一般都会建议在内表创建索引,以此下降Nested-Loop Join算法的开销,MySQL数据库中使用较多的就是这种算法,如下称为INLJ。来看这种算法的伪代码:
For each row r in R do
lookupr in S index
if found s == r
Then output the tuple
因为内表上有索引,因此比较的时候再也不须要一条条记录进行比较,而能够经过索引来减小比较,从而加速查询。整个过程以下图所示:
能够看到外表中的每条记录经过内表的索引进行访问,由于索引查询的成本是比较固定的,故优化器都倾向于使用记录数少的表做为外表(这里是否又会存在潜在的问题呢?)。故INLJ的算法成本以下表所示:
上表Smatch表示经过索引找到匹配的记录数量。同时能够发现,经过索引能够大幅下降内表的Join的比较次数,每次比较1条外表的记录,其实就是一次indexlookup(索引查找),而每次index lookup的成本就是树的高度,即IndexHeight。
INLJ的算法并不复杂,也算简单易懂。可是效率是否能达到用户的预期呢?其实若是是经过表的主键索引进行Join,即便是大数据量的状况下,INLJ的效率亦是至关不错的。由于索引查找的开销很是小,而且访问模式也是顺序的(假设大多数汇集索引的访问都是比较顺序的)。
大部分人诟病MySQL的INLJ慢,主要是由于在进行Join的时候可能用到的索引并非主键的汇集索引,而是辅助索引,这时INLJ的过程又须要多一步Fetch的过程,并且这个过程开销会至关的大:
因为访问的是辅助索引,若是查询须要访问汇集索引上的列,那么必要须要进行回表取数据,看似每条记录只是多了一次回表操做,但这才是
因为访问的是辅助索引,若是查询须要访问汇集索引上的列,那么必要须要进行回表取数据,看似每条记录只是多了一次回表操做,但这才是INLJ算法最大的弊端。首先,辅助索引的index lookup是比较随机I/O访问操做。其次,根据index lookup再进行回表又是一个随机的I/O操做。因此说,INLJ最大的弊端是其可能须要大量的离散操做,这在SSD出现以前是最大的瓶颈。而即便SSD的出现大幅提高了随机的访问性能,可是对比顺序I/O,其仍是慢了不少,依然不在一个数量级上。