1、等值连接:显性连接和隐性连接

在《MySQL必知必会》中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字。如下例两种方式是“等同”的。
//WHERE方式
SELECT
vend_name,
prod_name,
prod_price,
quantity
FROM
vendors,
products,
orderitems
WHERE
vendors.vend_id = products.vend_id
AND
orderitems.prod_id = products.prod_id;
14
 
1
//WHERE方式 
2
SELECT
3
  vend_name,
4
  prod_name,
5
  prod_price,
6
  quantity
7
FROM
8
  vendors, 
9
  products,
10
  orderitems
11
WHERE
12
  vendors.vend_id = products.vend_id
13
  AND
14
  orderitems.prod_id = products.prod_id;

//INNER JOIN方式
SELECT
vend_name,
prod_name,
prod_price,
quantity
FROM
(vendors INNER JOIN products ON vendors.vend_id = prodcuts.vend_id)
INNER JOIN orderitems ON orderitems.prod_id = products.prod_id;
9
 
1
//INNER JOIN方式
2
SELECT
3
  vend_name,
4
  prod_name,
5
  prod_price,
6
  quantity
7
FROM
8
  (vendors INNER JOIN products ON vendors.vend_id = prodcuts.vend_id)
9
    INNER JOIN orderitems ON orderitems.prod_id = products.prod_id;

其中,WHERE方式我们称之为隐性连接,而INNER JOIN方式我们称之为显性连接。这两者是有区别的,而上面我们说的“等同”,是指两者在结果集上是等同的,实际上在执行过程上却是不同的。

之前我们提到过SQL语句的执行过程,实际上都会产生笛卡儿积,都会有一个虚拟表,用来暂时保存执行结果,以作为下一步的输入。另外,ON过滤的执行顺序是在WHERE之前的,所以这就导致两者的执行过程区别在于:
  • 隐性连接,在FROM过程中对所有表进行笛卡儿积,最终通过WHERE条件过滤
  • 显性连接,在每一次表连接时通过ON过滤,筛选后的结果集再和下一个表做笛卡儿积,以此循环

这么久了,我们终于要说到SQL性能的主题上来了。那么以上,这两种执行方式会导致什么问题呢?假如有三张表做等值连接,每张表都有1000行数据,那么:
  • 隐性连接,做所有表的笛卡儿积,共1000*1000*1000=1亿 行数据,再通过WHERE过滤,也就是说,三张表连接最终扫描的数据量高达1亿
  • 显性连接,先做头两张表的笛卡儿积1000*1000=100万 行数据,通过ON条件筛选后的结果集(可能不到1000行)再和第三张表1000行数据做笛卡儿积

也就是说,显性连接最终做笛卡儿积的数量,根据之前表间ON后的结果,可能会远远小于隐性连接所要扫描的数量,所以同样是等值连接,显性连接的效率更高



2、EXPLAIN

2.1 驱动表

有的人可能会疑惑,不对啊,你这么说来,显性连接和隐性连接的差距不是一点半点,为什么我测试出来,两者的执行效率却几乎是等同的呢?

这是因为数据库引擎捣的鬼,这里以MySQL举例,在MySQL中,表间关联的算法是Nest Loop Join,即JOIN是通过嵌套循环来实现的。而你所写SQL的连表顺序(非OUTER类型)并不是实际执行的连表顺序,因为数据库会针对表情况进行自动优化,以小的结果集来驱动大的结果集,我们也常说以小表驱动大表。

也就是说,假如你有三张表,你写下SQL的JOIN顺序是A JOIN B ON ... JOIN C ON ...,其中表A有1000条数据,表B有100条数据,表C只有10条数据,实际上在执行的时候,很可能是先扫描数量最少的表C,然后是表B,最后是表A,中途遇到符合ON条件过滤的则执行筛选。为什么?

数据库不傻,我们说过表连接时通过嵌套循环来实现的,从第一个表中取出第一条,和第二个表中所有记录进行匹配,再取出第二条,和第二个表中所有记录进行匹配,以此循环。这里的第一个表,我们就称之为驱动表

如果驱动表越大,意味着外层循环次数就越多,那么被驱动表的访问次数自然也就越多(如驱动表和被驱动表数据分别为10条和100条,那么被驱动表访问次数为10次;如果分别是100条和10条,被驱动表访问次数则为100次),而每次访问被驱动表,即使需要的逻辑 IO 很少,循环次数多了,总量也不可能小,而且每次循环都不能避免消耗CPU,所以 CPU 运算量也会跟着增加。最终,这就意味着SQL性能的消耗,表现在查询时间变长。

就像你去超市买东西,总共都是买1000件东西,我让你买100件就付款一次,共付款10次;或者买10件就付款一次,共付款100次,哪个更累人?

 
所以,现在我们已经明白了,原来数据库在执行我们的SQL的时候,是会对执行顺序进行优化调整的。另外,要注意的是,这里的驱动表,并不是说数据量小的就是驱动表,我们刚才也提过,如果仅仅以表的大小来作为驱动表的判断依据,假若小表过滤后所剩下的结果集比大表多很多,结果就会在嵌套循环中带来更多的循环次数,这种情况小表驱动大表反而是低效率了。

所以,驱动表是由结果集的数据量来决定的:
  • 指定了连接条件时,满足查询条件的记录行数少的表为驱动表
  • 未指定连接条件时,行数少的表为驱动表
 
所以,准确地说,要想效率高,是要以小结果集驱动大的结果集。

2.2 EXPLAIN

那么,如何知道SQL优化后是如何执行SQL查询顺序的呢?这就要使用到MySQL中的关键字EXPLAIN了。命令主要作用是输出MySQL的优化器对SQL的执行计划,即MySQL会解释如何处理输入的SQL(是否使用索引,使用哪个索引,多表以什么顺序及什么关联字段做JOIN)

我们说想要SQL执行效率高,就要以小结果集驱动大结果集,而EXPLAIN的提示就可以帮助我们确认SQL执行时优化器是否会以合理的顺序来JOIN多张表。

EXPLAIN的使用很简单,直接加在SELECT之前即可,它不会真正去执行SQL,只是做分析处理。如下:
EXPLAIN
SELECT *
FROM
(SELECT * from t_rank AS r JOIN csic_delegation_dict AS dele ON r.commonCode_Delegation = dele.DELEGATION_CODE) tmp1
JOIN csic_event AS eve ON tmp1.commonCode_Event = eve.EVENT
5
 
1
EXPLAIN
2
SELECT *
3
FROM
4
    (SELECT * from t_rank AS r JOIN csic_delegation_dict AS dele ON r.commonCode_Delegation = dele.DELEGATION_CODE) tmp1 
5
        JOIN csic_event AS eve ON tmp1.commonCode_Event = eve.EVENT

EXPLAIN命令会为SQL中出现的每张表返回一行信息来说明数据库优化器将会如何操作这张表,返回的信息以表呈现,共有10个字段,如下示例: 
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+
| 1 | PRIMARY | eve | ALL | NULL | NULL | NULL | NULL | 441 | |
| 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 504 |Using where|
| 2 | DERIVED | dele | ALL | NULL | NULL | NULL | NULL | 41 | |
| 2 | DERIVED | r | ALL | NULL | NULL | NULL | NULL | 539 |Using where|
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+
8
 
1
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+
2
| id | select_type | table      | type   | possible_keys     | key     | key_len | ref  | rows | Extra     |
3
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+
4
|  1 | PRIMARY     | eve        | ALL    | NULL              | NULL    | NULL    | NULL |  441 |           |
5
|  1 | PRIMARY     | <derived2> | ALL    | NULL              | NULL    | NULL    | NULL |  504 |Using where|
6
|  2 | DERIVED     | dele       | ALL    | NULL              | NULL    | NULL    | NULL |   41 |           |
7
|  2 | DERIVED     | r          | ALL    | NULL              | NULL    | NULL    | NULL |  539 |Using where|
8
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-----------+

下面对这些字段做个简单的说明(更多详情可以参考官方文档):

2.2.1 id

SELECT语句的标识字段,若SQL中只有1个SELECT语句,则该值为1,否则依次递增;若SQL是UNION的结果,则该值为NULL。

值得一提的是,官方文档中并没有提到在多个SELECT语句时,即id有多个不同值时,哪个先执行,哪个后执行。那么如何去认识这个顺序呢?结合网友和一些简单测试的判断看来,大概是这样的:
  • id值较大的,执行优先级较高,且从上到下执行,且id值最大的组中,第一行为驱动表,如上图的table dele
  • id值相同时,认为是一组,执行顺序从上到下

当然,这可能多少有不严谨的地方,只能以后在使用过程中再根据实际场景去做进一步的判别了。先留个坑吧。

2.2.2 select_type

该字段用于说明SELECT语句的类型:
该字段的值         含义
SIMPLE 简单的SELECT,不适用UNION或子查询等
PRIMARY 查询中包含任何复杂的子部分,最外层的SELECT标记为PRIMARY
UNION UNION中的第二个或后面的SELECT语句
DEPENDENT UNION UNION中的第二个或后面的SELECT语句,取决于外面的查询
UNION RESULT UNION的结果
SUBQUERY 子查询中的第一个SELECT
DEPENDENT SUBQUERY 子查询中的第一个SELECT,取决于外面的查询
DERIVED 派生表的SELECT,FROM子句的子查询
UNCACHEABLE SUBQUERY 一个子查询的结果不能被缓存,必须重新评估外链接的第一行

2.2.3 table

用于表示数据集来自哪张表,其值一般是表名,但:
  • 当数据集市UNION的结果时,其值可能是<UNION M,N>,这里的M或N是id字段的值
  • 当数据集来自派生表的SELECT,则显示的是derived*,这里的*是id字段的值,如:
mysql> EXPLAIN SELECT * FROM (SELECT * FROM ( SELECT * FROM t1 WHERE id=2602) a) b;
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
| 1 | PRIMARY | <derived2> | system | NULL | NULL | NULL | NULL | 1 | |
| 2 | DERIVED | <derived3> | system | NULL | NULL | NULL | NULL | 1 | |
| 3 | DERIVED | t1 | const | PRIMARY,idx_t1_id | PRIMARY | 4 | | 1 | |
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
8
 
1
mysql> EXPLAIN SELECT * FROM (SELECT * FROM ( SELECT * FROM t1 WHERE id=2602) a) b;
2
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
3
| id | select_type | table      | type   | possible_keys     | key     | key_len | ref  | rows | Extra |
4
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+
5
|  1 | PRIMARY     | <derived2> | system | NULL              | NULL    | NULL    | NULL |    1 |       |
6
|  2 | DERIVED     | <derived3> | system | NULL              | NULL    | NULL    | NULL |    1 |       |
7
|  3 | DERIVED     | t1         | const  | PRIMARY,idx_t1_id | PRIMARY | 4       |      |    1 |       |
8
+----+-------------+------------+--------+-------------------+---------+---------+------+------+-------+

2.2.4 type

该字段表示MySQL在表中找到所需行的方式,又称“访问类型”,常见的有:
该字段的值     含义
ALL     遍历全表
index 与ALL的区别在于只遍历索引树
range 表示只操作单表,且符合查询条件的记录不止1条
ref     表明本步执行计划操作的数据集中关联字段是索引字段,但不止1条记录符合上步执行计划操作的数据集的关联条件
eq_ref     表明本步执行计划操作的数据集中关联字段是索引字段,且只有1条记录符合上步执行计划操作的数据集的关联条件
const      表明上述"table"字段代表的数据集中,最多只有1行记录命中本步执行计划的查询条件
system system只是const值的一个特例,它表示本步执行计划要操作的数据集中只有1行记录

2.2.5 possible_keys

该字段的值是可能被MySQL用作索引的字段,若值为NULL,则没有字段会被用作索引,因此查询效率不会高,这种情况下,需要优化数据表的索引结构。

2.2.6 key

该字段的值是MySQL真正用到的索引。

2.2.7 key_len

该字段的值表明上述key字段的length,当MySQL将某联合索引字段作为SQL执行时用到的索引时,key_len字段可以暗示MySQL真正在什么程度上(多长的最左前缀匹配字段)使用了该联合索引。若key字段的值为NULL,则key_len字段值也为NULL。

2.2.8 ref

该字段的值表明数据表中的哪列或哪个constants会被用于与key字段指定的索引做比较。

2.2.9 rows

该字段的值表明MySQL执行该步计划对应的查询时扫描的行数,该值是估算值,不完全准确。这个值对于SQL优化非常具有参考意义,通常情况下,该值越小查询效率越高

2.2.10 Extra

该字段的值包含了MySQL执行query时的其它额外信息。常见如下(查询效率由高到低):
该字段的值        
含义

Using index 表示使用索引,如果只有 Using index,说明他没有查询到数据表,只用索引表就完成了这个查询,这个叫覆盖索引。如果同时出现Using where,代表使用索引来查找读取记录, 也是可以用到索引的,但是需要查询到数据表。
Using where 表示条件查询,如果不读取表的所有数据,或不是仅仅通过索引就可以获取所有需要的数据,则会出现 Using where。如果type列是ALL或index,而没有出现该信息,则你有可能在执行错误的查询:返回所有数据。
Using filesort 不是“使用文件索引”的含义!filesort是MySQL所实现的一种排序策略,通常在使用到排序语句ORDER BY的时候,会出现该信息。
Using temporary 表示为了得到结果,使用了临时表,这通常是出现在多表联合查询,结果排序的场合。



3、STRAIGHT_JOIN


由上我们已经知道,EXPLAIN的提示可以帮助我们意识到哪些字段应该建索引,也可以帮我们确认SQL执行时,数据库优化器是否会以合理的顺序来JOIN多张表。


但有时候并不是说数据库优化器做的执行顺序就是最优的,而按照我们的FROM表的顺序执行才是最优的,那么问题来了:如果想让优化器以FROM语句列出的表顺序做JOIN,怎么办?

这里就要用到 STRAIGHT_JOIN 关键字了,将其加在SELECT关键字之后,用来告诉优化器按照FROM列出的表顺序进行JOIN。

举个例子,我有某个SQL如果按照优化器的执行顺序执行,则:
 
SQL查询时间为9s:


使用STRAIGHT_JOIN关键字,要求按照FROM表顺序进行执行,则:
 

SQL查询时间为0.2s:

 

但是仍然需要引起注意的是,这种方式因为执行顺序被固化了,那么随着时间的推移,数据库中的数据分布随着业务开展而发生变化,很可能导致原本运行顺畅的SQL逐渐变得糟糕。


另外,测试时为了保证结果的正确性,应避免查询缓存,需要每次执行前手动清除:

RESET QUERY CACHE;
 
1
RESET QUERY CACHE;



4、参考链接



附件列表

浅谈SQL优化入门:2、等值连接和EXPLAIN(MySQL)的更多相关文章

  1. 浅谈SQL优化入门:3、利用索引

    0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直 ...

  2. 浅谈SQL优化入门:1、SQL查询语句的执行顺序

    1.SQL查询语句的执行顺序 (7) SELECT (8) DISTINCT <select_list> (1) FROM <left_table> (3) <join_ ...

  3. 浅谈sql优化

    问题的发现:      菜鸟D在工作的时候发现项目的sql语句很怪,例如 : select a.L_ZTBH, a.D_RQ, a.VC_BKDM, (select t.vc_name from tb ...

  4. c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程

    c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...

  5. 浅谈SQL Server内部运行机制

    对于已经很熟悉T-SQL的读者,或者对于较专业的DBA来说,逻辑的增删改查,或者较复杂的SQL语句,都是非常简单的,不存在任何挑战,不值得一提,那么,SQL的哪些方面是他们的挑战 或者软肋呢? 那就是 ...

  6. 浅谈SQL Server数据内部表现形式

    在上篇文章 浅谈SQL Server内部运行机制 中,与大家分享了SQL Server内部运行机制,通过上次的分享,相信大家已经能解决如下几个问题: 1.SQL Server 体系结构由哪几部分组成? ...

  7. 浅谈SQL Server---2

    浅谈SQL Server内部运行机制 https://www.cnblogs.com/wangjiming/p/10098061.html 对于已经很熟悉T-SQL的读者,或者对于较专业的DBA来说, ...

  8. 浅谈SQL Server---1

    浅谈SQL Server优化要点 https://www.cnblogs.com/wangjiming/p/10123887.html 1.SQL Server 体系结构由哪几部分组成? 2.SQL ...

  9. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

随机推荐

  1. JavaScript 加号运算符详解

    将介绍JavaScript中 '+'加号运算符在一元.二元运算时的表现. 目录 1.一元运算符 2. 二元运算符 1. 一元运算符 语法: + Expression 说明:'+'号运算符作为一元运算符 ...

  2. servlet以及HTML中路径问题

    路径问题: ①相对路径和绝对路径: 绝对路径:绝对路径是以/开头的路径! 相对于当前服务器的绝对路径:如果是服务器解析,那么/就代表当前服务器的绝对路径:http://localhost:8080 相 ...

  3. ios逆向过程中lldb调试技巧-po篇

    假如你准备在模拟器里面运行这个,你可以在"(lldb)"提示的后面输入下面的: (lldb) po $eax LLDB在xcode4.3或者之后的版本里面是默认的调试器.假如你正在 ...

  4. 80C51 数码管动态显示0~7

    所使用的开发板 普中科技HC6800-ES V2.0 PC:win7 64位 编译软件: keil uversion2 烧写工具: 普中科技开发的PZ-ISP V1.82 烧写方式:热烧写 #incl ...

  5. 跨server传输数据注意事项

    我们需要模拟客服端 首先导入相关的jar包 文件,Jersey的相关jar包 实现客服端的代码为: @Test    public  void testClient() {        //图片生成 ...

  6. python基础学习(十二)

    模块 前面有简单介绍如何使用import从外部模块获取函数并且为自己的程序所用: >>> import math >>> math.sin(0) #sin为正弦函数 ...

  7. Python验证码通过pytesser识别

    Python安装包: 需要安装的包主要有两个: PIL 和 pytesser .tesseract (1).安装PIL:下载地址:http://www.pythonware.com/products/ ...

  8. App测试中 ----------------Android和IOS测试区别

    1 . Android长按home键呼出应用列表和切换应用,然后右滑则终止应用:2. 多分辨率测试,Android端20多种,ios较少:3. 手机操作系统,Android较多,ios较少且不能降级, ...

  9. java代码块的理解

    最近在复习java基础,在看到java代码块的时候,忽然发现自己貌似对于java代码块一无所知,于是赶紧对着一些资料实战演练了一把. 对于java代码块,不难根据名称看出其实就是一些java语句的集合 ...

  10. Centos 6修复/boot目录及fstab等系统文件

    author:JevonWei 版权声明:原创作品 错误界面 系统修复过程中,若需要修复fatab挂载文件,磁盘分区为lvm逻辑卷格式,则默认在修复模式下处于不可活动状态,需使用vgchage -ay ...