前言
说起数据库,想必一些朋友会认为,数据库不就是天天CRUD吗?只要我掌握了这几招,根本不在话下。是的,其实我也很赞同这个观点,对于大多数应用程序来说,只掌握这些内容,是可以胜任日常的开发工作的。
然而我们的程序不可能始终停留在测试环境,随着生产环境数据量的增大,以及用户规模的增长,数据库就会出现一些性能方面的问题。所以接下来的一段时间内,我们会学习一些数据库性能优化方面的内容,希望可以带给大家一些启迪和成长。今天,我们就从最基本的子查询讲起。
概述
子查询的定义本身很简单,就是一种嵌套查询。最常见的例子就是我们有两张表students和classes,为了简化说明,此处没有遵守三大范式,表中的字段如下:
sutdents
id | stu_name | stu_age | class_id |
1 | 张三 | 15 | 1002 |
2 | 李四 | 15 | 1001 |
3 | 王五 | 16 | 1002 |
4 | 朱六 | 14 | 1003 |
classes
class_id | class_name |
1001 | 语文 |
1002 | 数学 |
1003 | 英语 |
此时我们想查询哪些学生选择了数学课,就可以使用子查询去查找结果:
SELECT stu_name FROM sutdents WHERE sutdents.class_id IN (SELECT class_id FROM classes WHERE class_name = '数学')
可以看出,我们在这里使用了IN操作符来关联子查询的结果,熟悉的伙伴一定清楚,还有一种子查询的方式叫做EXISTS,使用方式如下:
SELECT stu_name FROM sutdents s WHERE EXISTS (SELECT 1 FROM classes c WHERE s.class_id = c.class_id AND class_name = '数学')
两种方式的查询结果都是一致的,我们找出了哪些学生选择了数学课:
但实际上,这两种子查询的效率在不同的情况下是不一样的,下面我们来单独谈谈这两种方式的区别。
什么是IN
从字面意思来看,IN即为外部表的字段,使用内部嵌套查询的结果中作为条件去查询所需的结果。因此,使用IN时,内部嵌套的子查询只会查询一次,这里引申出一个概念,叫做:非关联子查询。
在我们上述的示例中,子查询会先从classes表中找出课程名称为“数学”的id,此时子查询只执行了一次。通过查询的结果,即数学课的id号,作为外部查询的条件,去查找最终的结果。
什么是EXISTS
同样的,顾名思义,EXISTS即为“存在”。也就是说,在嵌套查询时,外部表的信息,需要在内部表的结果中存在,才可以查询出结果。因此,使用EXISTS时,内部嵌套的子查询会查询多次,次数取决于外部表的记录条目,最终返回对应的结果给外部表,这个概念叫做:关联子查询。
在上述示例中,查询语句会先从students表中找出每一条记录对应的class_id,然后传递给内部的子查询,内部子查询使用该class_id进行匹配,去classes表中查找满足class_id和class_name为“数学”的记录,再返回给外部的表。在此过程中,只有完全匹配的记录,才会呈现最终的结果。
如何优化子查询
在以往的很多经验和一些文章中经常会提到,优化SQL语句,需要使用EXISTS来代替IN,即可达到效果。那么这句话究竟是不是正确的呢?
通过今天的学习,我们现在已经知道,这句话不完全正确,也不完全错误。究其根因,我们已经了解了IN和EXISTS的特性,所以结论如下:
1、如果一个语句中,子查询外部的表A,数量小于子查询内部的表B,那么此时,使用EXISTS的效率,是要大于IN的。这是因为EXISTS每次查询会循环表A中的数据,然后传入表B进行匹配,此种情况下,使用EXISTS循环次数少,需要查询的记录条目也更少。
2、反之,如果子查询外部的表A,数量大于子查询内部的表B,那么此时,使用IN的效率会更高。此时使用IN可以缩小要查找的记录范围,最终匹配表A时可以减少记录的大小。
总结
今天我们阐述了子查询的概念和一些简单的用法,以及一些优化方式。我们可以将子查询分为关联子查询和非关联子查询两种模式,分别对应IN和EXISTS的用法。最后我们在优化子查询时提到了使用哪种方式取决于外表和内表的大小关系,其实优化的重心在于使用小表来驱动大表的原则,这些你都学会了吗?
下一期我们会从数据库索引的角度来继续讲述数据库性能优化的方式,朋友们,我们下期再见!
您的点赞和在看是我创作的最大动力,感谢支持
公众号:wacky的碎碎念
知乎:wacky