1:为什么需要关注SQL的执行顺序?
看到这个问题,好多同学可能会说我能写出来正确的SQL,然后SQL能跑出结果就行了,为什么还要关心SQL的执行顺序呢?那么我们先回答这个问题,如果仅仅考虑能跑出结果就满意了,那确实没必要关注SQL的执行顺序。然而好多时候我们我们不是SQL不对,而是SQL跑了半天结果却没出来,机器没崩溃,可能你先崩溃了。所以,如果你想比别人快那么一点点的话,那就认真了解下SQL的执行顺序,这是我们写出漂亮SQL的关键。另外好多数据岗位的初级面试也会问到这个问题,不要以为这个问题很简单,实际情况好多人回答的都是你SQL语句从上到下的顺序。
2:先看一段经典的SQL
卖完关子我们来正式介绍SQL的执行顺序。以下面的SQL为例来说明具体的执行顺序
3:正向看SQL的执行顺序
SQL不长也不复杂,但是把我们前面提到的SQL的六脉神剑都用上了。我们把其中的关键字提取出来,
SELECT,DISTINCT, FROM, ON, JOIN,WHERE,GROUP BY,HAVING,ORDER BY,LIMIT。
执行顺序:
FROM,为什么它是第一位呢?因为我们首先必须知道数据在哪?把数据取出来来以后才能做后续的操作。这里就是把student表和score表做笛卡尔积(笛卡尔积我们也有介绍过,具体参考SQL的六脉神剑),两个表笛卡尔积后生成临时虚拟表tmp1,当然如果是单表的话就直接把该表数据加载到内存即可。
ON ,这里的ON 是JOIN后面的ON,ON的作用是根据JOIN的条件把临时表tmp中不符合st.id=sc.sid的记录给过滤掉,只保留满足条件的记录,这样数据量就会减少很多,经过这一步形成虚表tmp2。
JOIN,这里JOIN的作用是根据JOIN的类型不同来对虚表tmp2做行的增减。具体来说,如果是LEFT (OUTER) JOIN就把上一步舍弃的左表的记录补回来,对应右表的数据用NULL填充;如果是RIGHT(OUTER) JOIN的话就把右表删除的记录补回来,同理FULL (OUTER) JOIN分别做类似的增加记录操作,但是(INNER) JOIN不会,所以这一步的操作主要针对的是OUTER JOIN,对应我们上面的SQL就是把student表中不等于score表中对应ID的记录给增补回来,这样操作之后形成虚表tmp3。
WHERE ,where是对增减后的虚表tmp做数据过滤操作,注意这一步是对关联后的所有数据即虚表tmp3做过滤操作,这一步不在区分是那种类型的JOIN了。针对我们上面的SQL,这一步的操作就是把tmp3中sex属性值不是’男‘的记录给剔除掉,只保留符合条件的记录,这一步操作后形成虚表tmp4,所以我们想要过滤掉最终结果的时候一般条件都是放到where里面的。
GROUP BY,经过上面四步的操作,一般都可以取到符合我们要求的明细数据了,而group by 是对数据做聚合操作,必须放在正确的结果集后执行。试想,如果没有取到正确的结果就做聚合操作那么最终的结果一定也是不正确的。针对上面的SQL,按照学生的年龄分组后,形成虚表tmp5。
HAVING,这一步也是过滤操作,只不过是对group by 聚合后的操作,所以它必然也就是在group by之后执行了,基本上到了这里我们的数据已经成型了,取出了满足了where条件的数据,然后按照group by 做了聚合,再根据having对结果集做了过滤,形成虚表tmp6。执行到这里算是已经成型了,再后面就是展示的问题了。针对上面的SQL,取出的是至少参见过一门考试的学生。
SELECT, 这一步是把上面经过层层过滤后的数据按照指定的列给筛选出来,上面的SQL就是只把我们用到的列age,和聚合后的列num取出来,形成虚表tmp7,包含age列和新的列num。
DISTINCT,这一步是对已经选择后的列,也即是虚表tmp7做指定列的去重操作。上面的SQL就是按照不同的学生ID做去重操作,形成虚表tmp8 。
ORDER BY,order by的作用是按照指定的列进行排序,他也必须是在已经满足需要的结果集上排序,而且是已经选择(select)的列,如果我们select 中没有这一列那么是无法进行排序的。上面的SQL是按照学生的年龄从低到高排序,然后形成结果集tmp9。
LIMIT,这是最后一步,对结果集做行上的处理,只不过这里是针对已经做完各种操作后的结果集做限制输出,所以它是放在最后执行,其实对整个执行过程而言没要减少复杂度,再看上面的SQL,取出来的是排序后的前100条记录,这就是最终的结果集tmp10,然后再把这个结果集输出出来。
4:逆向看SQL的执行顺序
上面十步就是一个典型SQL的执行过程,下面我们根据最终的结果来反推这整个执行过程帮助大家更好的记忆。
我们最终的需求是取出所有至少参加过一门考试的男生,按照年龄分组后正序排序的一组结果。那么由此倒推:
我们最后需要一个排序后的结果集,从中取出前100条,这是limit。
取前100条之前需要排序,这是order by。
排序执之前需要有结果列,这正常就是select,但是有些列不能直接统计,可能还需要一个去重操作,所以在select 之前还可能有distinct。
在select 之前我们需要把不满足条件的结果给剔除掉,所以如果是简单的明细数据,这一步之上就应该是where操作,但是如果是对聚合后的结果做过滤就需要在这之前有having,而having是对聚合结果做过滤,那么它之前必然就是group by了。
无论是对数据明细做过滤还是对聚合后的数据做过滤我们都依赖一个大的数据集,所以过滤之前要有join的操作,把多表先整合成一个大的数据集,供后面使用。
数据集从何而来的呢,这就是from的作用,所以from必须是第一位的,用来寻找数据。
这样一正一反是不是更加清晰明了?
5:一个SQL优化的实例
前面我们说了了解了SQL的执行顺序对于我们优化SQL有很大的帮助,下面就给大家举一个例子:
两条SQL的结果是一致的,在数据量不是很大的情况也执行效果差不多,但是如果是一个亿级的订单表和一个千万级的用户表关联的时候,B的效果要明显好于A的效果,因为根据SQL的执行顺序,B中,先在子查询中对需要join的两个表做了过滤,这样再join数据量就小了很多,而A中先join后过滤,开始的时候数据量很大,会影响执行的效果。怎么样,SQL的执行顺序还是很有用吧。