最近各种加班,忙于指标项目的测试,终于有时间闲下来看书了。
这次的学习笔记是第八章,调优技巧,闲话不多说,开记!
一、使用UNION代替OR
当SQL语句中同时有or和子查询,这种情况爱查询无法展开(unnest),只能走FILTER。这时将or改为union
例:
SELECT * FROM
FROM T1
WHERE OWERN = 'SCOTT'
OR OBJECT_ID IN
(SELECT OBJECT_ID FROM T2);
/* 改写为 */
SELECT * FROM T1 WHERE OWER= 'SCOTT'
UNION
SELECT * FROM T1 WHERE OBJECT_ID IN (SELECT OBJECT_ID FROM T2);
此时需要将T2.OBJECT_ID建立索引即可。
二:分页语句优化
1、首先,应该明确分页语句的SQL框架:
SELECT *
FROM (SELECT *
FROM (SELECT A.*, ROWNUM RN
FROM (需要分页的SQL) A )
WHERE ROWNUM <= 10)
WHERE RN >= 1
先利用ROWNUM进行编号,如果需求1-10数据,先过滤掉大量数据, 取小于等于10的数据,这时只扫描10行就停止了,然后再取大于等于1的。
其次,如果需要分页的SQL中有排序,我们要利用索引已经排序的特性,将order by的列包含在索引中,同事也要利用rownum的COUNT STOPKEY特性来优化SQL。如果没有排序,直接利用rownum优化。
如果分页语句中出现了SORT ORDER BY,意味着分页语句中没有利用到索引已经排序的特性,这时候需要创建正确的索引。
那么创建索引的时候,要优先将等值过滤和排序列组合在一起,然后再将非等值过滤列放后面。排序列的索引,正序倒序创建要和SQL中保持一致,前后顺序也要保持一致。
2、以上都是针对非分区表,那么如果要是分区表又该如何优化呢?
如果是没有筛选条件(个人理解不跨区应该也行),分区恰好是范围分区,由于范围分区每个分区的数据都是递增的,这时候的索引创建为local索引即可。但是如果分区是LIST分区或者HASH分区,这时候就必须要创建global索引,因为数据分布是无序的。
3、如果多表关联的分页语句呢?在单表分页的优化思想上,考虑到表关联即可。
A表与B表关联,如果走的是NL嵌套循环,驱动表扫描N次,就传值N次给被驱动表,达到排序的过滤条件后SQL就会停止。
如果是HASH链接呢?那这时两表关联后的结果集是无法保证依旧有序,这时候就需要关联后再次排序,执行计划中还是会出现SORT ORDER BY,所以不能走HASH,也不能走排序合并连接。
为什么多表关联要走NL呢?如果驱动表返回的数据是有序的,那么关联后的结果集也是有序的,这样就可以消除SORT ORDER BY。
如果A表与B表关联,排序列既有A表字段又有B表字段,这种情况只能关联完之后在进行排序,无法消除SORT ORDER BY,所以这种SQL无法优化,两表之间也只能走HASH。
之前讨论的A NL B 这种关联,order by字段应该是驱动表的,也就是A表。那么A NL B,order by的字段是被驱动表的呢?这种分页SQL无法优化。所以只有一个表排序,要将排序列所的所在表作为驱动表,并且控制驱动表返回的数据顺序和排序顺序一致,并将其余表的连接列建立索引。
同时,分页语句中也不能有ditinct , group by , max , min , avg , union , union all等关键字,如果分页语句中有这类关键字了,需要等表关联或者数据都跑完后再来分页,这样性能很差。
三、使用分析函数优化自连接
以下SQL中上半部分EMP表访问了两次,我们可以用分析函数,让表只访问一次,EMP表越大越明显。
SELECT ENAME,DEPTNO,SAL
FROM EMP A
WHERE SAL = (SELECT MAX(SAL) FROM EMP B
WHERE A.DEPTNO = B.DEPTNO);
/* 改写 */
SELECT ENAME, DEPTNO, SAL
FROM SELECT (A.* MAX(SAL) OVER(PARTITION BY DEPTNO) MAX_SAL FROM EMP A)
WHERE SAL = MAX_SAL;
四、超大表和超小表关联优化
A表30MB,B表30G,两表关联后返回大量数据,应该走HASH连接,因为A表小,可以作为驱动表,大表B作为被驱动表。因为小表的话,PGA可以放的下。
又由于B表特别大,想要加快查询速度,就要开启并行查询。将小表(驱动表)广播(HINT:pq_distribute(驱动表 none , broadcast) )到所有的查询进程,然后对大表进行并行随机扫描,每个进程都查询B表的一部分,最后将每个结果集并起来。
SELECT * FROM A,B1 WHERE A.OBJECT_ID = B1.OBJECT_ID --并行进行
UNION ALL
SELECT * FROM A,B2 WHERE A.OBJECT_ID = B2.OBJECT_ID --并行进行
UNION ALL
SELECT * FROM A,B3 WHERE A.OBJECT_ID = B3.OBJECT_ID --并行进行
UNION ALL
SELECT * FROM A,B4 WHERE A.OBJECT_ID = B4.OBJECT_ID --并行进行
UNION ALL
SELECT * FROM A,B5 WHERE A.OBJECT_ID = B5.OBJECT_ID --并行进行
UNION ALL
SELECT * FROM A,B6 WHERE A.OBJECT_ID = B6.OBJECT_ID --并行进行
查看a表并行广播的执行计划
EXPLAIN PALN FOR
SELECT
/*+ parallel(6) use(hash(a,b) pq_distribibute(a none,broadcast) */
FROM A, B
WHERE A.BOJECT_DT = B.OBJECT_ID
此时执行计划会出现PX SEND BROADCAST关键词。注意:如果两个大表关联,千万不能让大表广播。
这块广播的用法和详细介绍,我再去查查学学的。
五、超大表和超大表关联优化
超大表和超大表进行关联的时候,返回大量数据,应该走HASH连接。但是任意一张表PGA都无法存下,这时候就会溢出到磁盘。
此时可以开启并行查询加快查询速度,将两表的连接列进行HASH运算,将运算结果放进PGA中再进行HASH连接。这种并行HASH连接叫做并行HASH HASH连接。HINT:pq_distribute(被驱动表hash,hash)
此时执行计划中会出现PX SEND HASH的关键字。人工实现并行HASH HASH的过程如下:
1、先创建新表P1,在表A添加子一个字段HASH_VALUE,同时根据HASH_VALUE进行LIST分区
2、同时创建新表P2,在表B添加子一个字段HASH_VALUE,同时根据HASH_VALUE进行LIST分区
注意:两个表分区必须一样,如果分区不一样就会有数据关联不上
3、将A表数据迁移到表P1,B表数据迁移到P2中
INSERT INTO P1
SELECT ORA_HASH(OBIECT_ID, 4), A.* FROM A; --注意排除OBJECT_ID为null的数据
COMMIT;
INSERT INTO P2
SELECT ORA_HASH(OBIECT_ID, 4), B.* FROM B; --注意排除OBJECT_ID为null的数据
COMMIT;
4、并行HASH HASH关联的人工实现
SELECT *
FROM P1,P2
WHERE P1.OBJECT_ID = P2.OBJECT_ID
AND P1.HASH_VALUE = 0
AND P2.HASH_VALUE = 0;
SELECT *
FROM P1,P2
WHERE P1.OBJECT_ID = P2.OBJECT_ID
AND P1.HASH_VALUE = 1
AND P2.HASH_VALUE = 1;
SELECT *
FROM P1,P2
WHERE P1.OBJECT_ID = P2.OBJECT_ID
AND P1.HASH_VALUE = 2
AND P2.HASH_VALUE = 2;
SELECT *
FROM P1,P2
WHERE P1.OBJECT_ID = P2.OBJECT_ID
AND P1.HASH_VALUE = 3
AND P2.HASH_VALUE = 3;
SELECT *
FROM P1,P2
WHERE P1.OBJECT_ID = P2.OBJECT_ID
AND P1.HASH_VALUE = 4
AND P2.HASH_VALUE = 4;
这里运用了ORA_HASH函数,ORA_HAS(列,桶),HASH默认的桶为4294967295,可以设置为0 到4294967295。
ORA_HASH(OBJECT_ID,4)就是把OBJECT_ID的值进行HASH运算,然后放到0 1 2 3 4这几个桶中,这样P1 P2两个表就相同的桶间关联。
六、LIKE语句的优化
SELECT * FROM T WHERE OBJECT_NAME LIKE '%SEQ%';
上述SQL需要对字符两边进行模糊匹配,而索引根块和分支块存储的是前缀数据,也就是'SEQ%' 这种才会走索引。
所以此时只能全表扫描,如果加HINT走索引的话,也是INDEX FULL SCAN,此时是单块读,而且上述SQL还会有产生回表,性能有可能还不如全表扫描,表太大的话,两种执行计划都会很慢。
那么此时手动创建一个表当索引用,来代替INDEX FAST FULL SCAN不能回表的情况,用多块读来解决。
SELECT *
FROM T
WHERE ROWID IN (SELECT RID FROM INDEX_T WHERE OBJECT_NAME LIKE '%SEQ%');
改写完SQL后,需让INDEX_T和T表走嵌套循环,同时让INDEX_T作为驱动表,这样就达到了让INDEX_T充当索引的目的。
SELECT /*+ leading(index_t@a) use_nl(index_t@a,t) */
*
FROM T
WHERE ROWID IN (SELECT /*+ qb_name(a) */
RID
FROM INDEX_T
WHERE OBJECT_NAME LIKE '%SEQ%');
七、DBLINK优化
如果有两个表,大表a为远端表,小表b为本地表
默认情况下a表会传输到本地,在进行关联,此时a表在网络传输上会耗费很多时间。
那么b表很小,我们可以将b表发送至远端,关联后将结果返回本地即可,此时使用HINT:driving_sit
SELECT /*+ driving_sit(a) */ FROM A@DBLINK, B
WHERE A.ID = B.ID;
当这种这种情况的时候,我们可以设置arraysize来优化,减少网络交互次数,从而减少网络开销。
如果远端和本地表都很大呢?我们可以在本地创建一个带有DBLINK的物化视图,将远端表数据刷新到本地后在进行关联。
八、对标进行ROWID切片
如果对一个特别大的非分区表表进行UPDATE或者DELETE的时候,还只在一个会话里运行的话,很容易引发UNDO不够,如果会话中断,会产生大量的数据从UNDO中回滚,这还是走的单块,慢死。
那么此时可以对表进行ROWID切片,然后多个窗口同时执行SQL,这样既能加快执行速度又能减少对UNDO的占用。
Oracle有一个内置函数DBMS_ROWID.ROWID_CRATE()用于生成ROWID。对于一个非分区表,一个表就是一个段,段是由多个区组成,每个区里的块物理上是连续的。所以可以使用数据字典DBA_EXTENTS,DBA_OBJECTS关联,然后利用生成的ROWID的内置函数人工生成ROWID。