分享

sql语句的执行顺序以及流程(详细掌握)

 excel05 2018-07-16

程序员对sql语句的执行顺序的透彻掌握,是避免编程中各种bug和错误,歧义语句的不二法则。


  1. SELECT DISTINCT
  2. FROM
  3. JOIN
  4. ON
  5. WHERE
  6. GROUP BY
  7. HAVING
  8. ORDER BY
  9. LIMIT

如果你知道每个关键字的意思,作用,如果你还用过的话,那再好不过了。但是,你知道这些语句,它们的执行顺序你清楚么?如果你非常清楚,你就没有必要再浪费时间继续阅读了;如果你不清楚,非常好,你应该庆幸你阅读到了这么好的一篇文章。

准备工作

  1. 新建一个测试数据库TestDB;
  2. create database TestDB;
  3. 创建测试表table1和table2;
  4. CREATE TABLE table1
  5. (
  6. customer_id VARCHAR(10) NOT NULL,
  7. city VARCHAR(10) NOT NULL,
  8. PRIMARY KEY(customer_id)
  9. )ENGINE=INNODB DEFAULT CHARSET=UTF8;
  10. CREATE TABLE table2
  11. (
  12. order_id INT NOT NULL auto_increment,
  13. customer_id VARCHAR(10),
  14. PRIMARY KEY(order_id)
  15. )ENGINE=INNODB DEFAULT CHARSET=UTF8;
  16. 插入测试数据;
  17. INSERT INTO table1(customer_id,city) VALUES(''163'',''hangzhou'');
  18. INSERT INTO table1(customer_id,city) VALUES(''9you'',''shanghai'');
  19. INSERT INTO table1(customer_id,city) VALUES(''tx'',''hangzhou'');
  20. INSERT INTO table1(customer_id,city) VALUES(''baidu'',''hangzhou'');
  21. INSERT INTO table2(customer_id) VALUES(''163'');
  22. INSERT INTO table2(customer_id) VALUES(''163'');
  23. INSERT INTO table2(customer_id) VALUES(''9you'');
  24. INSERT INTO table2(customer_id) VALUES(''9you'');
  25. INSERT INTO table2(customer_id) VALUES(''9you'');
  26. INSERT INTO table2(customer_id) VALUES(''tx'');
  27. INSERT INTO table2(customer_id) VALUES(NULL);
  28. 准备工作做完以后,table1和table2看起来应该像下面这样:
  29. mysql> select * from table1;
  30. +-------------+----------+
  31. | customer_id | city |
  32. +-------------+----------+
  33. | 163 | hangzhou |
  34. | 9you | shanghai |
  35. | baidu | hangzhou |
  36. | tx | hangzhou |
  37. +-------------+----------+
  38. 4 rows in set (0.00 sec)
  39. mysql> select * from table2;
  40. +----------+-------------+
  41. | order_id | customer_id |
  42. +----------+-------------+
  43. | 1 | 163 |
  44. | 2 | 163 |
  45. | 3 | 9you |
  46. | 4 | 9you |
  47. | 5 | 9you |
  48. | 6 | tx |
  49. | 7 | NULL |
  50. +----------+-------------+
  51. 7 rows in set (0.00 sec)
  52. 准备SQL逻辑查询测试语句
  53. SELECT a.customer_id, COUNT(b.order_id) as total_orders
  54. FROM table1 AS a
  55. LEFT JOIN table2 AS b
  56. ON a.customer_id = b.customer_id
  57. WHERE a.city = ''hangzhou''
  58. GROUP BY a.customer_id
  59. HAVING count(b.order_id) <>
  60. ORDER BY total_orders DESC;
  61. 使用上述SQL查询语句来获得来自杭州,并且订单数少于2的客户。

好吧,这些测试表和测试数据均来自《MySQL技术内幕:SQL编程》,这应该不算抄袭吧,借鉴借鉴啊。

万事俱备,只欠东风。接下来开始这篇文章最正式的部分吧。

SQL逻辑查询语句执行顺序

还记得上面给出的那一长串的SQL逻辑查询规则么?那么,到底哪个先执行,哪个后执行呢?现在,我先给出一个查询语句的执行顺序:


  1. (7) SELECT
  2. (8) DISTINCT
  3. (1) FROM
  4. (3) JOIN
  5. (2) ON
  6. (4) WHERE
  7. (5) GROUP BY
  8. (6) HAVING
  9. (9) ORDER BY
  10. (10) LIMIT

上面在每条语句的前面都标明了执行顺序号,不要问我怎么知道这个顺序的。我也是读各种“武林秘籍”才得知的,如果你有功夫,去阅读一下MySQL的源码,也会得出这个结果的。

好了,上面我标出了各条查询规则的执行先后顺序,那么各条查询语句是如何执行的呢?这就是我今天这篇博文的重点内容。Go on…

执行FROM语句

在这些SQL语句的执行过程中,都会产生一个虚拟表,用来保存SQL语句的执行结果(这是重点),我现在就来跟踪这个虚拟表的变化,得到最终的查询结果的过程,来分析整个SQL逻辑查询的执行顺序和过程。

第一步,执行FROM语句。我们首先需要知道最开始从哪个表开始的,这就是FROM告诉我们的。现在有了两个表,我们到底从哪个表开始,还是从两个表进行某种联系以后再开始呢?它们之间如何产生联系呢?——笛卡尔积

关于什么是笛卡尔积,请自行Google补脑。经过FROM语句对两个表执行笛卡尔积,会得到一个虚拟表,暂且叫VT1(vitual table 1),内容如下:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 9you | shanghai | 1 | 163 |
  6. | baidu | hangzhou | 1 | 163 |
  7. | tx | hangzhou | 1 | 163 |
  8. | 163 | hangzhou | 2 | 163 |
  9. | 9you | shanghai | 2 | 163 |
  10. | baidu | hangzhou | 2 | 163 |
  11. | tx | hangzhou | 2 | 163 |
  12. | 163 | hangzhou | 3 | 9you |
  13. | 9you | shanghai | 3 | 9you |
  14. | baidu | hangzhou | 3 | 9you |
  15. | tx | hangzhou | 3 | 9you |
  16. | 163 | hangzhou | 4 | 9you |
  17. | 9you | shanghai | 4 | 9you |
  18. | baidu | hangzhou | 4 | 9you |
  19. | tx | hangzhou | 4 | 9you |
  20. | 163 | hangzhou | 5 | 9you |
  21. | 9you | shanghai | 5 | 9you |
  22. | baidu | hangzhou | 5 | 9you |
  23. | tx | hangzhou | 5 | 9you |
  24. | 163 | hangzhou | 6 | tx |
  25. | 9you | shanghai | 6 | tx |
  26. | baidu | hangzhou | 6 | tx |
  27. | tx | hangzhou | 6 | tx |
  28. | 163 | hangzhou | 7 | NULL |
  29. | 9you | shanghai | 7 | NULL |
  30. | baidu | hangzhou | 7 | NULL |
  31. | tx | hangzhou | 7 | NULL |
  32. +-------------+----------+----------+-------------+

总共有28(table1的记录条数 * table2的记录条数)条记录。这就是VT1的结果,接下来的操作就在VT1的基础上进行。

执行ON过滤

执行完笛卡尔积以后,接着就进行ON a.customer_id = b.customer_id条件过滤,根据ON中指定的条件,去掉那些不符合条件的数据,得到VT2表,内容如下:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | 9you | shanghai | 3 | 9you |
  7. | 9you | shanghai | 4 | 9you |
  8. | 9you | shanghai | 5 | 9you |
  9. | tx | hangzhou | 6 | tx |
  10. +-------------+----------+----------+-------------+

VT2就是经过ON条件筛选以后得到的有用数据,而接下来的操作将在VT2的基础上继续进行。

添加外部行

这一步只有在连接类型为OUTER JOIN时才发生,如LEFT OUTER JOIN、RIGHT OUTER JOIN和FULL OUTER JOIN。在大多数的时候,我们都是会省略掉OUTER关键字的,但OUTER表示的就是外部行的概念。

LEFT OUTER JOIN把左表记为保留表,得到的结果为:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | 9you | shanghai | 3 | 9you |
  7. | 9you | shanghai | 4 | 9you |
  8. | 9you | shanghai | 5 | 9you |
  9. | tx | hangzhou | 6 | tx |
  10. | baidu | hangzhou | NULL | NULL |
  11. +-------------+----------+----------+-------------+

RIGHT OUTER JOIN把右表记为保留表,得到的结果为:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | 9you | shanghai | 3 | 9you |
  7. | 9you | shanghai | 4 | 9you |
  8. | 9you | shanghai | 5 | 9you |
  9. | tx | hangzhou | 6 | tx |
  10. | NULL | NULL | 7 | NULL |
  11. +-------------+----------+----------+-------------+

FULL OUTER JOIN把左右表都作为保留表,得到的结果为:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | 9you | shanghai | 3 | 9you |
  7. | 9you | shanghai | 4 | 9you |
  8. | 9you | shanghai | 5 | 9you |
  9. | tx | hangzhou | 6 | tx |
  10. | baidu | hangzhou | NULL | NULL |
  11. | NULL | NULL | 7 | NULL |
  12. +-------------+----------+----------+-------------+

添加外部行的工作就是在VT2表的基础上添加保留表中被过滤条件过滤掉的数据,非保留表中的数据被赋予NULL值,最后生成虚拟表VT3。

由于我在准备的测试SQL查询逻辑语句中使用的是LEFT JOIN,过滤掉了以下这条数据:

| baidu | hangzhou | NULL | NULL |

现在就把这条数据添加到VT2表中,得到的VT3表如下:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | 9you | shanghai | 3 | 9you |
  7. | 9you | shanghai | 4 | 9you |
  8. | 9you | shanghai | 5 | 9you |
  9. | tx | hangzhou | 6 | tx |
  10. | baidu | hangzhou | NULL | NULL |
  11. +-------------+----------+----------+-------------+

接下来的操作都会在该VT3表上进行。

执行WHERE过滤

对添加外部行得到的VT3进行WHERE过滤,只有符合的记录才会输出到虚拟表VT4中。当我们执行WHERE a.city = ''hangzhou''的时候,就会得到以下内容,并存在虚拟表VT4中:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | 163 | hangzhou | 2 | 163 |
  6. | tx | hangzhou | 6 | tx |
  7. | baidu | hangzhou | NULL | NULL |
  8. +-------------+----------+----------+-------------+

但是在使用WHERE子句时,需要注意以下两点:

  1. 由于数据还没有分组,因此现在还不能在WHERE过滤器中使用where_condition=MIN(col)这类对分组统计的过滤;
  2. 由于还没有进行列的选取操作,因此在SELECT中使用列的别名也是不被允许的,如:SELECT city as c FROM t WHERE c=''shanghai'';是不允许出现的。

执行GROUP BY分组

GROU BY子句主要是对使用WHERE子句得到的虚拟表进行分组操作。我们执行测试语句中的GROUP BY a.customer_id,就会得到以下内容:


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | 163 | hangzhou | 1 | 163 |
  5. | baidu | hangzhou | NULL | NULL |
  6. | tx | hangzhou | 6 | tx |
  7. +-------------+----------+----------+-------------+

得到的内容会存入虚拟表VT5中,此时,我们就得到了一个VT5虚拟表,接下来的操作都会在该表上完成。

执行HAVING过滤

HAVING子句主要和GROUP BY子句配合使用,对分组得到的VT5虚拟表进行条件过滤。当我执行测试语句中的HAVING count(b.order_id) <>


  1. +-------------+----------+----------+-------------+
  2. | customer_id | city | order_id | customer_id |
  3. +-------------+----------+----------+-------------+
  4. | baidu | hangzhou | NULL | NULL |
  5. | tx | hangzhou | 6 | tx |
  6. +-------------+----------+----------+-------------+

这就是虚拟表VT6。

SELECT列表

现在才会执行到SELECT子句,不要以为SELECT子句被写在第一行,就是第一个被执行的。

我们执行测试语句中的SELECT a.customer_id, COUNT(b.order_id) as total_orders,从虚拟表VT6中选择出我们需要的内容。我们将得到以下内容:


  1. +-------------+--------------+
  2. | customer_id | total_orders |
  3. +-------------+--------------+
  4. | baidu | 0 |
  5. | tx | 1 |
  6. +-------------+--------------+

不,还没有完,这只是虚拟表VT7。

执行DISTINCT子句

如果在查询中指定了DISTINCT子句,则会创建一张内存临时表(如果内存放不下,就需要存放在硬盘了)。这张临时表的表结构和上一步产生的虚拟表VT7是一样的,不同的是对进行DISTINCT操作的列增加了一个唯一索引,以此来除重复数据。

由于我的测试SQL语句中并没有使用DISTINCT,所以,在该查询中,这一步不会生成一个虚拟表。

执行ORDER BY子句

对虚拟表中的内容按照指定的列进行排序,然后返回一个新的虚拟表,我们执行测试SQL语句中的ORDER BY total_orders DESC,就会得到以下内容:


  1. +-------------+--------------+
  2. | customer_id | total_orders |
  3. +-------------+--------------+
  4. | tx | 1 |
  5. | baidu | 0 |
  6. +-------------+--------------+

可以看到这是对total_orders列进行降序排列的。上述结果会存储在VT8中。

执行LIMIT子句

LIMIT子句从上一步得到的VT8虚拟表中选出从指定位置开始的指定行数据。对于没有应用ORDER BY的LIMIT子句,得到的结果同样是无序的,所以,很多时候,我们都会看到LIMIT子句会和ORDER BY子句一起使用。

MySQL数据库的LIMIT支持如下形式的选择:

LIMIT n, m

表示从第n条记录开始选择m条记录。而很多开发人员喜欢使用该语句来解决分页问题。对于小数据,使用LIMIT子句没有任何问题,当数据量非常大的时候,使用LIMIT n, m是非常低效的。因为LIMIT的机制是每次都是从头开始扫描,如果需要从第60万行开始,读取3条数据,就需要先扫描定位到60万行,然后再进行读取,而扫描的过程是一个非常低效的过程。所以,对于大数据处理时,是非常有必要在应用层建立一定的缓存机制。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多