SQL优化简介
一般在应用中, 糟糕的SQL语句是造成系统性能低下的最主要原因,例如大小写的不统一、同样的SQL语句不同的写法等。而且,随着数据量的增加,情况会变得越来越严重。(题外话:优秀的Oracle数据库优化人才,是任何公司都稀缺的)
SQL优化又称SQL调节,其步骤一般包括:
SQL调节的目标
SQL调节包括三大目标:降低负载、均衡负载和并行化负载。
l降低负载:即寻找更高效的途径来完成相同的功能
如某个非大表(小于2000万行数据数据或小于2G大小的单表),常规查询需要访问的数据实践中90%情况下是不会超过20%的,此时建立合理的索引是有效的方法之一
l均衡负载:即应该把任务分时段均衡调度
如一般系统白天是访问高峰,如果此时备份任务、批处理任务或报表数据抽取任务也 挤在这个时段则易造成负载峰
值现象,正确的做法应该是把备份任务、批处理任务和报表数据抽取任务放到晚上进行处理,或采用并行化策略
l并行化负载:即大数据量的查询访问需要使用并发策略
如在数据仓库环境中应该多使用并发策略,此举可以明显减少响应时间
SQL优化阶段
使用OEM发现顶级SQL
在OEM中,选择性能->其它监视链接->定级活动,如下图:
不要用*代替所有列名
指定仅仅需要的列名与使用*对比:
时间:359/1327=27.05% CUP耗费: 4092121327/6413227637=63.81%
IO耗费: 29601/110117=26.88% 可见大幅降低I/O从而降低响应时间!
SQL优化技巧
使用TRUNCATE代替DELETE
Oralce执行DELETE后会使用UNDO表空间存放被删除的信息以便恢复,如果之后用户使用ROLLBACK而不是COMMIT,则Oralce将利用该UNDO表空间中的数据进行恢复。当使用TRUNCATE时,Oracle不会将删除的数据放入UNDO表空间,因而速度要快很多。当要删除某个表中的全部数据时,应该使用TRUNCATE而不是不带WHERE条件的DELETE。语法如下:
TRUNCATE TABLE table_name [DROP|REUSE STORAGE]
DROP STORAGE为默认的方式,表示收回被删除的表空间
REUSER STORAGE表示保留被删除的空间以供该表的新数据使用
应用开发中,可以编写一个子程序让其动态的清除空表,以供调用。
默认PCTFREE为10,假定为5,high-water mark是一个存储段分配多少存储器的标记。
活用COMMIT
PL/SQL块中,经常将几个相互联系的DML语句写在BEGIN …END,如果不影响事务的完整性,则建议在每个END前面写一个COMMIT,以达到 对DML的及时提交和 释放事务所占的资源的目的。
COMMIT释放的资源包括:
lUNDO段上用于恢复数据的信息
l事物中DML语句获得的锁
lSGA中重做日志缓冲区中的空间
lOracle为管理相关资源(如上述资源) 而开销的内部资源
体验例子流程如下 :
体验例子显示 :
减少表的查询次数
1.一个逻辑单元中,将能读出的列一次性读出,且尽量存放在本地变量中,应该杜绝不要用一个读一个
2.在包含子查询的SQL中,要特别注意减少对表的查询次数,在代码清晰时对于能减少查询次数的应坚决减少,举例如下:
3.执行计划如下,结论是什么?
以EXISTS代替DISTINCT
多表信息的查询时,避免在SELECT子句中使用DISTINCT. 一般可以考虑用EXISTS替换, EXISTS 使查询更为迅速,因为此时RDBMS核心模块将在子查询的条件一旦满足后,立刻返回结果。
优化前:
优化后:
使用默认值
使用默认之后的执行时间比为1.063/2.657=40.01%,快了一倍多!
可见在不含默认值,是null的列上没有使用索引,是全表扫描!而使用了默认值的列上使用了索引范围扫描!
l不能用null作索引,任何包含null值的列都将不会被包含在索引中。即使索引有多列的情况下,只要这些列中有一列含有null,该列就会从索引中排除。也就是说如果某列存在空值,即使对该列建索引也不会提高性能
l任何在where子句中使用is null或is not null的语句优化器是不允许使用索引的
l如果每列确实可能存在空值的情况,可以使用默认值的方式替代以便充分利用索引提高性能
使用DECODE函数减少处理步骤
l使用DECODE函数可以避免重复扫描相同记录或重复连接相同的表.
lDECODE函数也可以运用于GROUP BY 和ORDER BY子句中.
l上述例子有两步相似的操作,使用DECODE后节省一半时间,如果一组相似的操作越多,节省的时间则越多,计算公式为n-1,其中n为相似操作的步骤数
通配符的使用技巧
上例中已知数据%DX_ACCOUNT_TRADE%,只有以I开头的
:0.031/1.891=1.639%
l当通配符出现在LIKE后面字符串的首位时,索引将不会被使用,因此在已知某字符的情况下,LIKE查询中应尽量不要把通配符写在首位
l%代表不定长的字符,_代表定长的字符,如果在确定要通配的字符长度时,应该尽量使用_,而不是%
定义并执行严格的SQL编写规范
使用Oracle共享游标的优点是:
l降低和减少Oracle对SQL的解析数量
l动态调整内存
l提高内存的使用率
风格请参照前面章节中的“建议的程序风格”
表的连接方式
FROM表顺序选择
使用基于规则的优化器(CBO)时,Oracle解析器按照从右到左的顺序处理FROM子句的表明,即FROM子句中最后的表(驱动表)会最先被处理。
当FROM子句包含多个表时,建议将记录最少的表(一般是字典表)放在最后面。当Oracle处理多个表时,一般采用排序或合并的方式连接这些表,系统首先会扫描FROM子句部分的最后一个表,并对该表的数据行进行排序;然后扫描倒数第二个表,并将从该表中取出的记录与第一个表中的记录进行匹配合并,依此类推。
如果是大于两表相关联,最好选择交叉表为驱动表,交叉表是指被其它表所引用的表。
RBO模式下,小表为驱动表的执行时间为大表是驱动的执行时间的:
0.078/2.253 = 2.26%!
驱动表的选择
此时的优化器模式为CBO,二者的执行时间仅仅相差:
0.328-0.313=0.015毫秒,二者几乎接近,这是为什么呢?我们再看二者执行计划:
我们发现,此时二者的执行计划 一模一样!这又是为什么?
驱动表的选择
驱动表(Driving Table)是指被最先访问的表,通常是以全表扫描的方式访问的。
如果优化器是CBO,则优化器会检查SQL语句中每个表的物理大小、索引状态,然后寻找开销最小的执行路径。如果优化器是RBO,且所有连接条件都有索引对应,则驱动表是FROM子句中最后一个表。
无论如何,我们建议始终将记录小的表(如字典表)作为驱动表,则能适应CBO和RBO!
WHERE子句如何写
Oralce优化器的原理是采用自下而上的顺序解析WHERE子句,因此表之间的连接必须写在其他WHERE条件之前, 可过滤掉最大数量记录的条件必须写在WHERE子句的末尾 。
上述SQL语句的例子虽然符合优化规范的比不符合优化规范的写法仅仅快了不到0.4秒,但重要的是这是在当前单机环境、且没有任何其它数据库事务、业务很简单、连接的表仅有两个表的情况下。如果在实际的大业务量环境下,则这种优化效应将成 倍数级增长!
因此,我们建议任何时候编写SQL语句时要 使用表的别名、 对表的连接永远 写在WHERE后面的第一个位置,并对过滤条件进行估算, 按照降序的大小将这些 条件从WHERE子句最后部分往前排列。
习题
1.SQL优化的步骤包含那几步?可以使用那几种工具或方法发现糟糕的SQL?
2.SQL调节的三大目标是什么?请举例来说明如何均衡负载。
3.一般来说,SQL优化包括三个阶段,分别是语法分析、优化和执行阶段。请问,语法分析阶段的主要任务是什么?Oracle在优化阶段优化器主要执行的任务和考虑的因素又分别包含哪些?
4.在SELECT少用*,多用具体的列名其理论依据是什么?
5.Truncate包含几种用法?画图并解释Truncate如何改变高水位线。
6.举例说明什么情况下应该尽量多用COMMIT,什么情况下不能。
7.使用DECODE合并多个相类似操作,其与减少对数据库的查询次数有关系吗?
8.应该避免那种不合适的通配符的使用方法?
9.解释什么是驱动表,应该如何选择驱动表。
10.推荐的FROM子句和WHERE子句应该如何写,并解释其原理。 |