今天我们来说说微信中的多表联合查询。是小表带动大表还是大表带动小表?1.VS中的exists在正式分析之前,我们先来看一下和exists中的两个关键字。假设我现在有两张表:employee表和department表,每个员工都有一个部门,employee表存储的是部门的id,这个字段是一个索引;department表有department的id和name等属性,其中id是主键,name是唯一索引。这里我直接用vhr中的表做实验,数据库脚本就不单独给大家了。您可以查看vhr项目(https://github.com/lenve/vhr)以获取数据库脚本。假设我要查询技术部门的所有员工。我有以下两种查询方式:第一种查询方式是使用in关键字查询:select*fromemployeeewheree.departmentIdin(selectd.idfromdepartmentdwhered.name='TechnicalDepartment')limit10;这条SQL简单易懂,相信大家都能看懂。查询时先查询里面的子查询(即先查询部门表),再执行外部查询。我们可以看一下它的执行计划:可以看到首先查询的是department表,如果没有索引就使用索引。索引扫描全表,然后查询employee表,也是使用索引来查询,整体效率比较高。第二种是使用exists关键字查询:select*fromemployeeewhereexists(select1fromdepartmentdwhered.id=e.departmentIdandd.name='技术部')limit10;这个SQL查询结果和上面使用in关键字的结果是一样的,只是查询过程不同。我们来看看这条SQL的执行计划:可以看到,先扫描employee表,然后使用employee表中的departmentId,再去department表中进行数据比对。在上面的SQL中,如果子查询有返回值,则表示为true,如果没有返回值,则表示为false。如果属实,员工记录将被保留。如果为假,员工记录将被丢弃。所以子查询中可以用SELECT*代替SELECT*,改成SELECT1或other。MySQL官方的说法是实际执行时会忽略SELECT列表,所以写什么区别不大。比较两个查询计划中扫描的行数,我们可以大致看出差异。使用in稍微更有效率。如果使用in关键字查询,先查询department表,再查询employee表。一般来说,部门表的数据比员工表的数据小,所以这是小表带动大表,效率更高。如果使用exists关键字查询,先查询employee表,再查询department表。一般来说,部门表的数据比员工表的数据小,所以这是大表带动小表,效率比较低。总之,小表带动大表的效率高,大表带动小表的效率会比较低。因此,假设department表的数据量大于employee表的数据量,以上两条SQL使用exists查询关键字的效率会更高。2、为什么小表带动大表?在MySQL中,这种多表联合查询的原理是:以驱动表的数据为基础,使用类似于我们Java代码中写的嵌套循环的方法来跟随驱动表。记录来匹配。以第一节中的表为例,假设我们的员工表E是一个有10000条记录的大表;部门表D是一个有100条记录的小表。假设D驱动E,那么执行过程大致是这样的:对于100个部门{匹配10000名员工(用于B+树搜索)},则搜索总数为100+log10000。假设E驱动D,那么执行过程大致是这样的:对于10000名员工{匹配100个部门(用于B+树搜索)}那么搜索总数为10000+log100。从这两个数据的对比可以看出,小表带动大表效率更高。核心原因是在查找被驱动表时,一般都有索引,索引的查找速度快很多,查找次数少。3.没有索引怎么办?上一节我们得出的结论有一个前提,就是驱动表和被驱动表关联的字段是有索引的。以我们之前的表为例,E表中保存了departmentId字段,对应D表中的id字段被删除,id字段是D表中的主键索引。如果id不是主键索引,是普通字段,那么D表是否也需要做全表扫描?那时,E驱动D或D驱动E几乎没有区别。对于驱动表上没有可用索引的情况,MySQL使用了一种称为BlockNested-LoopJoin(简称BNL)的算法。该算法的步骤如下:将E表的数据读入线程内存join_buffer中。扫描D表,取出D表中的每一行,与join_buffer中的数据进行比较。如果满足连接条件,它将作为结果集的一部分返回。朋友们,我们来看一下,如果我删除了E表中departmentId字段上的索引,然后删除了D表中id字段上的主键索引,我们来看下面的SQL执行计划:可以看到,此时表E和表D都是全表扫描。另外需要注意的是,这些比较操作都在内存中,所以执行效率还可以。但是,既然所有的数据都读入了内存,那能不能放到内存中呢?如果内存中没有空间怎么办?让我们看看上面的查询计划。在E表的查询中,Extra中也出现了Usingjoinbuffer(BlockNestedLoop)。Block不就是块的意思吗?所以意思就很明确了。如果一次装不下内存,就分块读,先读一部分进内存,比较完再把另一部分读进内存。我们可以通过以下命令查看join_buffer的大小:262144/1024=256KB默认大小为256KB。我现在将这个值增加,然后查看新的执行计划,如下:可以看到,此时没有Usingjoinbuffer(BlockNestedLoop)提示。总结一下:如果join_buffer足够大,一次性将所有数据读入内存,那么不管是大表驱动小表还是小表驱动大表。如果join_buffer的大小有限,建议小表驱动大表,这样即使需要分块读取,读取次数也较少。但是说实话,这种没有索引的多表联合查询,效率比较低,应该尽量避免。综上所述,多表联合查询时,建议小表带动大表。
