上篇的sql优化篇章,更多偏向于优化的思想概念,先前抛出的4个优化问题中,篇幅过长,只对前两个问题进行了解析。

接下来我们一起来谈谈sql的连接查询优化,更偏向于实际运用,并对如下两个问题进行探讨。篇幅过长,请耐心看完。

1.嵌套查询、HASH连接、排序合并连接、笛卡尔连接等怎样玩能达到最优?

2. IN  EXISTS 谁快谁慢?

嵌套循环(NESTED LOOPS)

嵌套循环的算法:驱动表返回一行数据,通过连接列传值给被驱动表,驱动表返回多少行,被驱动表就要被扫描多少次。

这里我补充一下驱动表和被驱动表:理解驱动表和被驱动表的本质,需要理解顺序读取和随机读取的差异,内存适合随机读取,硬盘则顺序读取的效率比较好。

驱动表,作为外层循环,若进行一次IO将所有数据读取,则适合顺序读取,一次性批量的把数据读取出来,不考虑缓存情况下。

被驱动表,即里层循环,由于需要不断的拿外层循环传进来的每条记录去匹配,所以如果是适合随机读取的,那么效率就会比较高。如果表上有索引,实际上就意味着这个表是适合随机读取的。

1.嵌套循环可以快速返回两表关联的前几条数据,如果SQL中添加了HINT:FIRST_ROWS,在两表关联的时候,优化器更倾向于嵌套循环。

2.嵌套循环驱动表应该返回少量数据。如果驱动表返回了100万行,那么被驱动表 就会被扫描100万次。这个时候SQL会执行很久,被驱动表会被误认为热点表,被驱动表连接列的索引也会被误认为热点索引。

3.嵌套循环被驱动表必须走索引。如果嵌套循环被驱动表的连接列没包含在索引中,那么被驱动表就只能走全表扫描,而且是反复多次全表扫描。当被驱动表很大的 时候,SQL就执行不出结果。

4.嵌套循环被驱动表的连接列基数应该很高。如果被驱动表连接列的基数很低,那么被驱动表就不应该走索引,这样一来被驱动表就只能进行全表扫描了,但是被驱动表也不能走全表扫描。

5.两表关联返回少量数据才能走嵌套循环。前面提到,嵌套循环被驱动表必须走索引,如果两表关联,返回100万行数据,那么被驱动表走索引就会产生100万次回表。回表一般是单块读,这个时候SQL性能极差,所以两表关联返回少量数据才能走嵌套 循环。

两表关联走不走NL(嵌套循环)是看两个表关联之后返回的数据量多少?还是看驱动表返回的数据量多少

如果两个表是1∶N关系,驱动表为1,被驱动表为N并且N很大,这时即使驱动表返回数据量很少,也不能走嵌套循环,因为两表关联之后返回的数据量会很多。

所以判断两表关联是否应该走NL应该直接查看两表关联之后返回的数据量,如果 两表关联之后返回的数据量少,可以走NL;返回的数据量多,应该走HASH连接。

SELECT * FROM t1,t2 WHERE t1.id = t2.id; 如果t1有200条数据,t2有200万行数据,t1与t2是1∶N关系,N很低,应该怎么优化SQL?

因为t1与t2是1∶N关系,N很低,我们可以在b的连接列(id)上创建索引,让 t1与t2走嵌套循环(t1 nl t2),这样t2表会被扫描100次,但是每次扫描表的时候走的 是id列的索引(范围扫描)。

如果让t1和t2进行HASH连接,t2表会被全表扫描(因为没有过滤条件),需要查询表中100万行数据,而如果让t1和t2进行嵌套循环,t2表只需要 查询出表中最多几百行数据(100*N)。

一般情况下,一个小表与一个大表关联,我们可以考虑让小表NL大表,大表走连接列索引(如果大表有过滤条件,需要将过滤条件与连接列组合起来创建组合索引),从而避免大表被全表扫描。

 

HASH连接(HASH JOIN )

HASH连接的算法:两表等值关联,返回大量数据,将较小的表选为驱动表,将驱动表的“select列和join列”读入PGA中的work area,然后对驱动表的连接列进行hash 运算生成hash table,当驱动表的所有数据完全读入PGA中的work area之后,再读取被驱动表(被驱动表不需要读入PGA中的work area),对被驱动表的连接列也进行 hash运算,然后到PGA中的work area去探测hash table,找到数据就关联上,没找到 数据就没关联上。哈希连接只支持等值连接。

 

如何优化HASH连接?

 

因为HASH连接需要将驱动表的select列和join列放入PGA中,所以,我们 应该尽量避免书写select * from....语句,将需要的列放在select list中,这样可以减少驱动表对PGA的占用,避免驱动表被溢出到临时表空间,从而提升查询性能。

如果无法避免驱动表被溢出到临时表空间,我们可以将临时表空间创建在SSD上或者RAID 0上,加快临时数据的交换速度。

当PGA采用自动管理,单个进程的work area被限制在1G以内,如果PGA采用手动管理,单个进程的work area不能超过2GB。如果驱动表比较大,比如驱动表有 4GB,可以开启并行查询至少parallel(4),将表拆分为至少4份,这样每个并行进程中 的work area能够容纳1GB数据,从而避免驱动表被溢出到临时表空间。

排序合并连接(SORT MERGE JOIN)

前文提到HASH连接主要用于处理两表等值关联返回大量数据。排序合并连接主要用于处理两表非等值关联,比如>,>=,<,<=,<>,但是不能用于instr、substr、like、regexp_like关联,instr、substr、like、regexp_like关联只能走嵌套循环。

排序合并连接的算法:两表关联,先对两个表根据连接列进行排序,将较小的表作为驱动表,然后从驱动表中取出连接列的值,到已经排好序的被驱动表中匹配数据,如果匹配上数据,就关联成功。驱动表返回多少行,被驱动表就要被匹配多少次,这个匹配的过程类似嵌套循环,但是嵌套循环是从被驱动表的索引中匹配数据,而排序合并连接是在内存中(PGA中的work area)匹配数据。

怎么优化排序合并连接?

 

如果两表关联是等值关联,走的是排序合并连接,我们可以将表连接方式改为HASH连接。如果两表关联是非等值关联,比如>,>=,<,<=,<>,这时我们应该先从业务上入手,尝试将非等值关联改写为等值关联,因为非等值关联返回的结果 集“类似”于笛卡儿积,当两个表都比较大的时候,非等值关联返回的数据量相当大。如果没有办法将非等值关联改写为等值关联,我们可以考虑增加两表的限制条件,将两个表数据量缩小,最后可以考虑开启并行查询加快SQL执行速度。

笛卡尔连接(CARTESIAN JOIN)

两个表关联没有连接条件的时候会产生笛卡儿积,这种表连接方式就叫笛卡儿连接。在多表关联的时候,两个表没有直接关联条件,但是优化器错误地把某个表返回的Rows算为1行(注意必是1行),这个时候也可能发生笛卡儿连接。

标量子查询(SCALAR SUBQURY)

当一个子查询介于select与from之间,这种子查询就叫标量子查询。

标量子查询类似一个天然的嵌套循环,而且驱动表固定为主表。大家是否还记得:嵌套循环被驱动表的连接列必须包含在索引中。同理,标量子查询中子查询的表连接列也必须包含在索引中。

我们建议在工作中,尽量避免使用标量子查询,假如主表返回大量数据,主表的连接列基数很高,那么子查询中的表会被多次扫描,从而严重影响SQL性能。如果主表数据量小,或者主表的连接列基数很低,那么这个时候我们也可以使用标量子查询,但是记得要给子查询中表的连接列建立索引。

当SQL里面有标量子查询,我们可以将标量子查询等价改写为外连接,从而使它 们可以进行HASH连接。

为什么要将标量子查询改写为外连接而不是内连接呢?因为标量子查询是一个传值的过程,如果主表传值给子查询,子查询没有查询到数据,这个时候会显示NULL。如果将标量子查询改写为内连接,会丢失没有关联上的数据。

半连接与反连接
 

半连接:两表关联只返回一个表的数据就叫半连接。半连接一般就是指的in和exists。在 SQL优化实战中,半连接的优化是最为复杂的。in和exists一般情况下都可以进行等价改写。

反连接:两表关联只返回主表的数据,而且只返回主表与子表没关联上的数据,这种连接就叫反连接。反连接一般就是指的not in和not exists。

 

需要注意的是,not in里面如果有null,整个查询会返回空,而in里面有null,查询不受null影响。所以在将not exists等价改写为not in的时候,要注意null。一般情况下,如果反连接采用not in写法,我们需要在where条件中剔除null。

FILTER:如果子查询(in/exists/not in/not exists)没能展开(unnest),在执行计划中就会产生FILTER,FILTER类似嵌套循环,FILTER的算法与标量子查询一模一样。

 

IN 与EXISTS 谁快谁慢?

 

如果执行计划中没有产生FILTER,那么我们可以参考以下思路:in与exists是半连接,半连接也属于表连接,那么既然是表连接,我们需要关心两表的大小以及两表之间究竟走什么连接方式,还要控制两表的连接方式,才能随心所欲优化SQL,而不是去记什么时候in跑得快,什么时候exists跑得快。

SQL 语句的本质:标量子查询可以改写为外连接(需要注意表与表之间关系,去重),半连接可以改写为内连接(需要注意表与表之间关系,去重),反连接可以改写为外连接(不需要注意表与表之间关系,也不需要去重)。

SQL语句中几乎所有的子查询都能改写为表连接的方式,所以我们提出这个观点:SQL语句其本质就是表连接(内连接与外连接),以及表与表之间是几比几 关系再加上GROPU BY。

SQL连接查询优化[姊妹篇.第五弹]的更多相关文章

  1. SQL查询与SQL优化[姊妹篇.第四弹]

    在上一篇文章中,我们一起了解了关系模型与关系运算相关的知识,接下来我们一起谈谈,面对复杂的关系数据,我们如何来优化,SQL如何玩转更优呢? 在上一篇中抛出了4个关于优化方面的问题: 1.返回表中0.0 ...

  2. SQL高性能查询优化语句(总结)

    SQL 高性能查询优化语句,一些经验总结 1.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where ...

  3. PL/SQL连接oracle数据库

    PL/SQL连接oracle数据库 1.简单介绍 在不安装oracle数据库的情况下使用pl/sql连接远程oracle数据库. 2.详细步骤: a)      安装PL/SQL.依据自己的操作系统安 ...

  4. SQL Server -- 回忆笔记(五):T-SQL编程,系统变量,事务,游标,触发器

    SQL Server -- 回忆笔记(五):T-SQL编程,系统变量,事务,游标,触发器 1. T-SQL编程 (1)声明变量 declare @age int (2)为变量赋值 (3)while循环 ...

  5. {MySQL的逻辑查询语句的执行顺序}一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析

    MySQL的逻辑查询语句的执行顺序 阅读目录 一 SELECT语句关键字的定义顺序 二 SELECT语句关键字的执行顺序 三 准备表和数据 四 准备SQL逻辑查询测试语句 五 执行顺序分析 一 SEL ...

  6. SQLServer SQL连接查询深度探险(摘录

    SQL连接查询深度探险[摘录] 测试环境: Windows XP Profession MySQL 5.0.45 Oracle 9i DB2 UDB 9.1 测试的SQL脚本如下:此脚本适合MySQL ...

  7. SQL Server 查询优化 索引的结构与分类

    一.索引的结构 关系型数据库中以二维表来表达关系模型,表中的数据以页的形式存储在磁盘上,在SQL SERVER中,数据页是磁盘上8k的连续空间,那么,一个表的所有数据页在磁盘上是如何组织的呢?分两种情 ...

  8. [SQL] SQL 基础知识梳理(五) - 复杂查询

    SQL 基础知识梳理(五) - 复杂查询 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5939796.html 序 这是<SQL 基础知识梳理( ...

  9. SQL连接

    SQL连接可以分为内连接.外连接.交叉连接. 数据库数据:             book表                                          stu表 1.内连接 ...

随机推荐

  1. 学习RabbitMQ(五)

    消息中间件就是在消息的传输过程中保存消息的容器.消息中间件再将消息从它的源中继到它的目标时充当中间人的作用.队列的主要目的是提供路由并保证消息的传递:如果发送消息时接收者不可用,消息队列会保留消息,直 ...

  2. Tomcat警告之“资源添加到Web应用程序[]的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间”

    原因 缓存不够,可以将其缓存调大 解决办法: 在 /conf/context.xml 的 前添加以下内容: <Resources cachingAllowed="true" ...

  3. pycharm2019版本可用破解

    1.下载破解文件 点击链接https://pan.baidu.com/s/1FbpXC4-rACrLMfeuVzJGPQ   提取码:hyed 下载补丁文件jetbrains-agent.jar并将它 ...

  4. 【静态页面架构】CSS之定位

    CSS架构 1.浮动: 是以float属性设置容器指定的位置 <style> div { width: 200px; height: 200px; } #qq { background-c ...

  5. background-clip 和 background-origin

    下面都是我自己的理解, 如果有不对的地方, 还请大家帮忙指出.下面是在 chrome 浏览器上测试的 background-clip 和 background-origin 先说说background ...

  6. 移动端调试工具weinre安装教程(java版)

    先申明:本安装教程是基于java的jdk安装的,经过测试可以正常使用,基于nodejs的安装,小喵鼓弄了好几天也没有成功,如果哪位童鞋基于nodejs安装成功了,请联系小喵,小喵在这里先谢谢你了! 好 ...

  7. Codepen 每日精选(2018-4-28)

    按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以打开原始页面. 页面目录特效https://codepen.io/suez/pen/k... 选单交互效果https:// ...

  8. JavaScript 工作原理之七-Web Workers 分类及 5 个使用场景

    Web Workers 分类及 5 个使用场景 原文请查阅这里,略有删减,本文采用知识共享署名 4.0 国际许可协议共享,BY Troland. 这是 JavaScript 工作原理的第七章. 本系列 ...

  9. 为什么局部内部类访问外边的局部变量必须为final

    6.局部内部类访问外边的局部变量时,此变量必须为final类型 马克-to-win:由于技术方面的限制,java的设计者们做出如下语法规定:局部内部类访问外边的局部变量时,此变量必须为final类型, ...

  10. java中如何能把一个字符串转成日期对象

    题目3.1: 把一个字符串转成日期对象 当我们想根据输入字符串得到一个日期对象时我们不知道,应该以什么格式写这个字符串,才能被系统正确解析,一种聪明的做法是,马克-to-win,我们先 把日期对象根据 ...