Oracle常用调优技巧

最近各种加班,忙于指标项目的测试,终于有时间闲下来看书了。

这次的学习笔记是第八章,调优技巧,闲话不多说,开记!

一、使用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的。

扫描二维码关注公众号,回复: 10971638 查看本文章

其次,如果需要分页的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。

发布了10 篇原创文章 · 获赞 1 · 访问量 396

猜你喜欢

转载自blog.csdn.net/s4cott/article/details/105181839
今日推荐