【开发】SQL优化思路(以oracle为例)

powered by wanglifeng https://www.cnblogs.com/wanglifeng717

单表查询的优化思路

单表查询是最简单也是最重要的模块,它是多表等查询的基础。

避免对数据重复扫描

能一次扫描拿到的数据,不要重复扫描,查一次库能解决的问题,最好不要多次查。数据的读取非常消耗资源,减少对数据块的扫描。

例如:

1.SELECT COUNT (*)

FROM employees

WHERE salary < 2000;

2.SELECT COUNT (*)

FROM employees

WHERE salary BETWEEN 2000 AND 4000;

3.SELECT COUNT (*)

FROM employees

WHERE salary>4000;

统计任务经常用的语句。其实每个语句基本都把全表或索引扫了一遍,既然要全扫,就把握机会,能一次搞定的就一次搞定。

改写成

SELECT COUNT (CASE WHEN salary < 2000 THEN 1 ELSE null END) count1,COUNT (CASE WHEN salary BETWEEN 2001 AND 4000 THEN 1 ELSE null END) count2,COUNT (CASE WHEN salary > 4000 THEN 1 ELSE null END) count3 FROM employees;

严格来说,我们不推荐写过度复杂“炫技”的SQL,不要生搬硬套示例,只是为了让大家有个“节省持家”的意识。

例如如下经典写法,通过object_id字段上的索引全扫一遍,拿到了多种类别信息,不要分三次查询。

select max(object_id),min(object_id),sum(object_id),avg(object_id),count(object_id) from t where object_id is not null;

从大表中获取少量数据

从大选小,索引是你的不二选择。

例如:select t.name,t.status from t where t.pay_order_id = 101803309910017574;

索引利用B+树的原理可以快速找到某条数据,所以如果你想在大表中找到某条数据,索引是你必须要使用的技术。如上例所示,通过在pay_order_id上索引快速锁定这条数据的rowid,通过回表找到其他字段 t.name,t.status。这条语句就可以迅速执行,即使是千万级别表。原因还是全表扫描读的块非常多,而索引锁定数据快,读的块非常少,所以时间很快。

如果表记录数很少,使用索引效率反而低。例如,只有几十条记录,所有数据在一个

block 内。则全表扫描只需 1 个 block 的 io,而索引读由于回表等可能需要几个 block。

从大表中获取部分数据

例如:select t.name,t.status from t where t.pay_order_id < 101803309910017574;

上例所示,执行计划可能是全表扫描,也可能走索引。

主要决定因素之一是oracle的代价计算(cost),如果数据量比较大,走索引读,每条数据都伴随着一次回表操作。而全表扫描可以一次读多个块进内存。两种方式相比之下,哪条路径的代价低,oracle就会选择哪条。

所以,全表扫描的速度不一定慢。如果上述的SQL没有满足你的性能需求,且需求不能变,导致SQL已经不能修改时,我们可以考虑能否消除索引的回表操作。无论表多大,结果集多大,一旦所要的数据在索引块中都能找到,就不需要回表。因为索引全扫的块肯定比全表扫的块少的多的多,oracle肯定走索引全扫。

例如:

create index t_union_uuid_order_id on t(pay_order_id,uuid);

select uuid,pay_order_id from t where t.pay_order_id<101803300910017574;

如上例所示,所要字段数据在组合索引块中都能找到,所以没有回表操作。而索引块的数量远远小于全表数据的块数量,即使索引全扫,性能也非常好。

绝大多数情况下,这条select t.name,t.status from t where t.pay_order_id < 101809910017574语句我们可以控制下结果集,让索引即使回表,代价也远低于全表扫描。

组合索引不推荐三个及以上的字段建立组合索引,如果需要的字段非常多,不方便建立组合索引,建议控制结果集,少量快速多次,索引或两字段组合索引,多手段结合使用。具体使用要具体问题具体分析。宗旨就是控制结果集,使得走索引的代价低于全表扫描,然后利用索引快速,读块少的优点提高效率。这样分批几次拿数据,可能速度比一次全拿还快。事实是结果集控制的好,往往全表扫描的效率都能满足需求,更何况是索引扫描。

从大表中获取大量数据

这种场景首先要反问的就是这个需求是否存在问题,是否真的适合用关系型数据库?如果确实有这种需求。大表的数据量往往是惊人的,只能分页去拿。而ORACLE的三层select分页会越分越慢。

SELECT *

FROM (SELECT TA.*, ROWNUM ROW_NUM

FROM (select UUID, pay_order_id

from t

order by pay_order_id) TA

WHERE ROWNUM <= 100)

WHERE ROW_NUM > 0;

主要矛盾就是内层的WHERE ROWNUM <= 100,随着页数增加,结果集越来越大。2.order by的排序非常耗费性能,尤其大结果集的排序。3. 外层的WHERE ROW_NUM > 0随着页数越来越大,需要过滤的结果集也越来越大。

推荐方式:

SELECT t.*

FROM (select uuid, pay_order_id

from t

where t.pay_order_id is not null【*注】如果没有非空约束必须显示标明,否则索引失效

and t.pay_order_id >= '101809020001428452'

order by t.pay_order_id) t

WHERE ROWNUM <= 100;

pay_order_id 字段的需求是只增不减,为了不重不漏必须排序。索引是有序的,我们想用索引抵消掉排序,所以要查看执行计划,必须要走到索引。WHERE ROWNUM <= 100在oracle优化中会被推到内层语句中。所以实际结果集是t.pay_order_id >= '101809020001428452'之后的100条数据。所以结果集控制住了,索引代价肯定低于全表扫描,肯定走索引,索引又抵消了排序,同时 WHERE ROWNUM <= 100;每页都是100,rownum的性能损耗也控制住了。

这样额外的代价是,程序每次要记住最后一条pay_order_id,下次分页的时候将其带入。

推广到其他应用则可以选择表中的create_time字段代替pay_order_id。

多表查询的优化思路

多表连接把握住连接方式

多表查询和单表查询,唯一不同的就是把握住连接方式,只要连接方式把握住,多表查询其实就是多次单表查询。

三种连接方式:

nested loops join拿驱动表的结果集,去连接另外一个表,类似于两重嵌套循环(典型使用:小表驱动大表)。

hash join 拿驱动表的结果集去做hash表,PGA区,结果集大了,会到磁盘里。

merge join 无驱动表的概念,较少用到,对于连接键有序。



powered by wanglifeng https://www.cnblogs.com/wanglifeng717



从原理图可以看出,循环嵌套连接和hash连接中驱动表非常关键,准确说驱动表的结果集非常关键。循环嵌套连接的结果集大了,双层循环非常低效,哈希连接结果集大了可能导致排序开销变大,PGA区放不下等问题。

驱动表是oracle自动选择的,默认是加了过滤条件后,结果集小的那个表。如果查看执行计划,驱动表不如你所愿,你需要检查结果集是否相比另一个表结果集来说,明显是小结果集。或者自动收集信息不准确,需要更新。

如果是多表连接查询少量数据,推荐走循环嵌套连接。

create index n_index_order_id on n(order_id);

create index t_index_query_id on t(query_id);

select t.id ,t.name,n.address from n, t where t.pay_order_id=n.order_id and t.query_id='261801163544557068';

在驱动表的过滤条件上建立索引,快速锁定需要的少量数据行,在被驱动表的连接字段上建立索引,方便连接条件迅速匹配。这样的配合,就算两个表都是千万级别的表,只要索引不失效,速度都非常快。

如果是多表连接要查询出一部分数据,推荐走哈希连接

首先过滤条件过滤出小结果集,小结果集是个相对的概念,有时1000条算小结果集,有时10条也算大结果集,这里的小结果集一般在百条量级。

哈希连接的特点就是,无论驱动表的结果集在一定范围内如何变化,理论上,一次查询的时间近似等于扫一遍被驱动表的时间。性能表现相当高效和稳定。

控制驱动表的结果集,在被驱动表的连接字段上建立索引,忽略回表等细节,确认走到索引,这样一次查询的时间近似等于被驱动表的索引全扫时间,而我们知道,索引块相对全表块是非常少的,索引全扫非常高效。

走哪种连接方式,是oracle自动选择的,oracle选择的规则就是基于上述原理,所以我们决定不了走哪种执行计划,但是我们能让oracle”不得不走”哪种执行计划。

控制住结果集

控制结果集,不仅体现在单表查询的索引选择问题,还有体现在多表查询的连接方式和效率上。

除此之外还存在很多误区。结果集的概念并不是简单的数据量,而是一种意识,有控制结果集的意识,而不是教条主义的定义多少数量算大结果集。

结果集经典示例:

把in换成exists就完事了,性能就优化了,这是常犯的误区。

in是判断一个值是否在某个列中,而exists是判断一个值是否存在

Select * from tab where id in ( select id from tabel );

In 是先产生子查询结果集,然后主查询区结果集中寻找符合要求的字段列表,符合要求的输出。

Exists不返回列表值,而是true或者false,运行方式为,先运行主查询一次,在去子查询中查询与之对应的结果,如果子查询返回true则输出,反之不输出,在根据主查询的每一行去子查询中查询。

从原理可以看出,如果in的子查询结果集很大,外层的结果集也很大,相当于两个大结果集在连接运算,很耗性能。

Exists的运算比in优化了,但是就是搜索内层子查询的时候优化了,但是关键点是要把握住内外层的结果集,如果结果集很大,exists同样很慢,结果集控制的好,in操作也能符合要求。

总结:不管你多有把握,请一定要看下执行计划,一定要看下执行计划,一定要看下执行计划。。。。

本文来自博客园,作者:wanglifeng,转载请注明原文链接:https://www.cnblogs.com/wanglifeng717/p/15847101.html

sql索引优化思路的更多相关文章

  1. paip.sql索引优化----join 代替子查询法

    paip.sql索引优化----join 代替子查询法 作者Attilax ,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn.n ...

  2. SQL索引优化方法

    SQL索引优化方法 以下是代码片段: ROW_NUMBER() OVER(ORDER BY ResumeCreateTime DESC) as [RowID] ,[TopDegree] ,[Degre ...

  3. SQL索引优化

    序言数据库的优化方法有很多种,在应用层来说,主要是基于索引的优化.本次秘笈根据实际的工作经验,在研发原来已有的方法的基础上,进行了一些扩充,总结了基于索引的SQL语句优化的降龙十八掌,希望有一天你能用 ...

  4. SQL性能优化思路

    1. 尽可能把数据的存储和计算放入Memory而不是Disk,且减少IO操作,比如运用Redis等缓存技术 2. 对数据表进行精心设计,特别是大数据表,对常用数据字段进行适当的冗余,尽可能避免分表导致 ...

  5. MySql设计规范及SQL索引优化【呕心之作】

    数据库及表结构基本设计规范 1. 所有表必须使用Innodb存储引擎 没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎(mysql5. ...

  6. sql server优化思路

    查询速度慢的原因很多,常见如下几种:    1.没有索引或者没有用到索引(这是查询慢最常见的问题,是程序设计的缺陷)    2.I/O吞吐量小,形成了瓶颈效应.    3.没有创建计算列导致查询不优化 ...

  7. SQL索引优化及实战文章总结(总结)

    1. MySQL索引原理以及查询优化 2.

  8. SQL语句优化 学习笔记

    sql语句时间花在哪了? 1 等待时间 2 执行时间 这两个时间并非孤立的,单条语句执行的快 其他语句等待的时间就少 执行时间花在哪了? 1 查找 沿着索引查找 慢者可能全表扫描 2 取出 查到行后, ...

  9. MySQL的B树索引与索引优化

    MySQL的MyISAM.InnoDB引擎默认均使用B+树索引(查询时都显示为"BTREE"),本文讨论两个问题: 为什么MySQL等主流数据库选择B+树的索引结构? 如何基于索引 ...

随机推荐

  1. BLOB 和 TEXT 有什么区别?

    BLOB 是一个二进制对象,可以容纳可变数量的数据.TEXT 是一个不区分大小写 的 BLOB. BLOB 和 TEXT 类型之间的唯一区别在于对 BLOB 值进行排序和比较时区分大小 写,对 TEX ...

  2. memcached 如何实现冗余机制?

    不实现!我们对这个问题感到很惊讶.Memcached 应该是应用的缓存层.它的设 计本身就不带有任何冗余机制.如果一个 memcached 节点失去了所有数据,您 应该可以从数据源(比如数据库)再次获 ...

  3. 学习GlusterFS(二)

    环境准备 3台机器,每个机器双网卡,每个机器还需要额外添加1个10GB的磁盘用于测试 机器系统版本是centos6.6 1 2 3 4 5 [root@gluster-1-1 ~]# uname -r ...

  4. Spark学习摘记 —— Pair RDD行动操作API归纳

    本文参考 参考<Spark快速大数据分析>动物书中的第四章"键值对操作",本篇是对RDD转化操作和行动操作API归纳的最后一篇 RDD转化操作API归纳:https:/ ...

  5. ACM - 最短路 - CodeForces 295B Greg and Graph

    CodeForces 295B Greg and Graph 题解 \(Floyd\) 算法是一种基于动态规划的算法,以此题为例介绍最短路算法中的 \(Floyd\) 算法. 我们考虑给定一个图,要找 ...

  6. Pandas查询数据的几种方法

    Pandas查询数据 Pandas查询数据的几种方法 df.loc方法,根据行.列的标签值查询 df.iloc方法,根据行.列的数字位置查询 df.where方法 df.query方法 .loc既能查 ...

  7. Linux基础学习 | 目录及文件

    一.目录结构 Linux目录采用树形结构,以根目录/向下延伸呈一个倒置的树的形状. 每个目录下都有其相对应的子目录,而子目录中又有其子目录的存在,就像一棵树的树枝分叉,所有的目录层次结构分明,每个目录 ...

  8. W3School 教程整理

    离线版大部分整理自 w3cschool ,少部分整理自 w3school ,由本人整理. 感谢 w3cschool 站长的无私奉献. 由于本人精力有限,此处的列表不一定能够及时更新,最新版本请到本人博 ...

  9. 【Android开发】控件外边框自定义

    1.在drawable里面新建自定义的资源文件shape <?xml version="1.0" encoding="utf-8"?> <sh ...

  10. Python中使用正则表达式获取两个字符中间部分

    问题背景:当我们爬取网页信息时,对于一些标签的提取是没有意义的,所以需要提取标签中间的信息. 解决办法:用到了re包下的函数 方法1:用到了research()方法和group()方法 方法2:用到了 ...