浅谈SQL优化入门:2、等值连接和EXPLAIN(MySQL)

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

在《MySQL必知必会》中对于等值连接有提到两种方式,第一种是直接在WHERE子句中规定如何关联即可,那么第二种则是使用INNER JOIN关键字。如下例两种方式是“等同”的。

  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;
  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,只是做分析处理。如下:

  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个字段,如下示例:

  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字段的值,如:
  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逐渐变得糟糕。


4、参考链接

时间: 2024-10-19 09:05:04

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

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

1.SQL查询语句的执行顺序 (7) SELECT (8) DISTINCT <select_list> (1) FROM <left_table> (3) <join_type> JOIN <right_table> (2) ON <join_condition> (4) WHERE <where_condition> (5) GROUP BY <group_by_list> (6) HAVING <having_

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

0.写在前面的话 关于索引的内容本来是想写的,大概收集了下资料,发现并没有想象中的简单,又不想总结了,纠结了一下,决定就大概写点浅显的,好吧,就是懒,先挖个浅坑,以后再挖深一点.最基本的使用很简单,直接就写在这里吧. 索引是众所周知的可以提高查询的速度,且针对的是具体的字段,使用方式为( 不具体指明则建立非聚集索引): CREATE INDEX <索引名> ON <表名(关系名)>; e.g. CREATE INDEX yearIndex ON movie(year); 而撤销索引

浅谈sql优化

问题的发现:      菜鸟D在工作的时候发现项目的sql语句很怪,例如 : select a.L_ZTBH, a.D_RQ, a.VC_BKDM, (select t.vc_name from tbkxx t where t.vc_code = a.vc_bkdm) vc_name, a.VC_BZ, a.L_SCLB, a.EN_ZS, a."ROWID", s."ROWID",--冗余列 decode(nvl(a.l_cjsl, 0), 0, 0, round

浅谈PHP优化顺序

开发工程中我们可能会为自己的编程优化步骤感到困惑,不知该从何做起,这里我也结合相关资料和自己的经验,简要谈谈PHP优化顺序,望网页们给予补充和改进.1.代码优化  代码优化就是是平时写代码的一些良好的习惯与技巧等等比如尽可能的静态化一个方法,加载文件竟可能给完  整路径,清楚一些相似功能函数的区别等等...2.工具优化    做数据缓存(memcacheds)和页面缓存达到页面静态化3.优化sql语句   关键字大写,使用缩: 分行格式书写sql,查询时尽量少用* : 多表查询的时候尽可能的使用

浅谈SQL Server任务调度

在前面两篇文章中( 浅谈SQL Server内部运行机制 and 浅谈SQL Server数据内部表现形式 ),我们交流了一些关于SQL Server的一些术语,SQL Sever引擎 与SSMS抽象模型,SQL Server内部存储机制和SQL Server内部体系结构等.讨论的这些问题,均可以归为一个问题,即"SQL Server是怎么执行客户端输入的SQL 语句的?”,其中,重点讨论了下图(SQL Server 体系结构) 然而,如果我们仅仅了解如上技术,是不具备一个资深DBA或数据库优化

浅谈网站优化六步骤

1.分析关键词 这是进行SEO最重要的一环,关键词分析包括:关键词关注量分析.竞争对手分析.关键词与网站相关性分析.关键词部署.关键词排名预测. 2.网站架构分析 网站结构符合搜索引擎的蜘蛛喜好则有利于SEO.其中网站架构分析包括:剔除网站架构不友好设计.尽量使用树状目录结构.网站导航与链接优化. 2.关键词布局 SEO不仅仅只让网站首页在搜索引擎有好的排名,更为重要的是让网站的每个页面都带来一定流量.所以我们要为每个页面单独建设独一无二的页面主题(title,description)以及页面正

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

前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都能登录了.不能这么写!” “呦?小伙子这都知道了?那你说说看 啥是注入?注入只能拿来绕过登录么?” 好吧,竟然在老子面前装逼,看来不给你点儿颜色看看,你还真是不明白天有多高.. 于是乎..哈哈.大清早的,轻松在班里装了一手好逼.. 呵呵.不说了,下面我把那个项目重写一下发上来吧.演示一下注入有哪些危

浅谈SQL注入风险 - 一个Login拿下Server(转)

前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都能登录了.不能这么写!” “呦?小伙子这都知道了?那你说说看 啥是注入?注入只能拿来绕过登录么?” 好吧,竟然在老子面前装逼,看来不给你点儿颜色看看,你还真是不明白天有多高.. 于是乎..哈哈.大清早的,轻松在班里装了一手好逼.. 呵呵.不说了,下面我把那个项目重写一下发上来吧.演示一下注入有哪些危

【sql注入】浅谈sql注入中的Post注入

[sql注入]浅谈sql注入中的Post注入 本文来源:i春秋学院 00x01在许多交流群中,我看见很多朋友对于post注入很是迷茫,曾几何,我也是这样,因为我们都被复杂化了,想的太辅助了所以导致现在感觉到难,现在,就让我们一起来谈谈,post注入是多么的轻松吧!PS:文中有写os-shell00x02测试站点:http://xxx.xxxxx.com/对于我来说,post注入无非有两种方法第一种利用burp抓包然后使用sqlmap -r 来进行检测注入第二种比较简单,直接使用sqlmap --