SQL优化——全自动SQL审核

发布于:2024-04-26 ⋅ 阅读:(39) ⋅ 点赞:(0)


我们为大家分享一些常用的全自动SQL审核脚本,在实际工作中,我们可以对脚本进行适当修改,以便适应自己的数据库环境,从而提升工作效率。

1、抓出外键没创建索引的表

此脚本不依赖统计信息。

建议在外键列上创建索引,外键列不创建索引容易导致死锁。级联删除的时候,外键列没有索引会导致表被全表扫描。以下脚本抓出Scott账户下外键没创建索引的表:

with cons as (select /*+ materialize */ owner, table_name, constraint_name
          from dba_constraints
         where owner = 'SCOTT'
           AND constraint_type = 'R'),
     idx as (
     select /*+ materialize*/ table_owner,table_name, column_name
          from dba_ind_columns
         where table_owner = 'SCOTT')
select owner,table_name,constraint_name,column_name
  from dba_cons_columns
 where (owner,table_name, constraint_name) in
       (select * from cons)
   and (owner,table_name, column_name) not in
       (select * from idx);

在Scott账户中,EMP表的deptno列引用了DEPT表的deptno列,但是没有创建索引,因此我们通过脚本可以将其抓出:
在这里插入图片描述

2、抓出需要收集直方图的列

此脚本依赖统计信息。当一个表比较大,列选择性低于5%,而且列出现在where条件中,为了防止优化器估算Rows出现较大偏差,我们需要对这种列收集直方图。以下脚本抓出Scott账户下,表总行数大于5万行、列选择性低于5%并且列出现在where条件中的表以及列信息:

select a.owner,
       a.table_name,
       a.column_name,
       b.num_rows,
       a.num_distinct Cardinality,
       round(a.num_distinct / b.num_rows * 100, 2) selectivity
  from dba_tab_col_statistics a, dba_tables b
 where a.owner = b.owner
   and a.table_name = b.table_name
   and a.owner = 'SCOTT'
   and round(a.num_distinct / b.num_rows * 100, 2) < 5
   and num_rows > 50000
   and (a.table_name, a.column_name) in
       (select o.name, c.name
          from sys.col_usage$ u, sys.obj$ o, sys.col$ c, sys.user$ r
         where o.obj# = u.obj#
           and c.obj# = u.obj#
           and c.col# = u.intcol#
           and r.name = 'SCOTT');

在Scott账户中,test表总行数大于5万行,owner列选择性小于5%,而且出现在where条件中,通过以上脚本我们可以将其抓出:
在这里插入图片描述

3、抓出必须创建索引的列

此脚本依赖统计信息。当一个表比较大,列选择性超过20%,列出现在where条件中并且没有创建索引,我们可以对该列创建索引从而提升SQL查询性能。以下脚本抓出Scott账户下表总行数大于5万行、列选择性超过20%、列出现在where条件中并且没有创建索引:

select owner,
       table_name,
       column_name,
       num_rows,
       Cardinality,
       selectivity
from (select a.owner,
             a.table_name,
             a.column_name,
             b.num_rows,
             a.num_distinct                              Cardinality,
             round(a.num_distinct / b.num_rows * 100, 2) selectivity
      from dba_tab_col_statistics a,
           dba_tables b
      where a.owner = b.owner
        and a.table_name = b.table_name
        and a.owner = 'SCOTT')
where selectivity >= 20
  and num_rows > 50000
  and (table_name, column_name) not in
      (select table_name, column_name
       from dba_ind_columns
       where table_owner = 'SCOTT'
         and column_position = 1)
  and (table_name, column_name) in
      (select o.name, c.name
       from sys.col_usage$ u,
            sys.obj$ o,
            sys.col$ c,
            sys.user$ r
       where o.obj# = u.obj#
         and c.obj# = u.obj#
         and c.col# = u.intcol#
         and r.name = 'SCOTT');

在这里插入图片描述

4、抓出SELECT * 的SQL

此脚本不依赖统计信息。在开发过程中,我们应该尽量避免编写SELECT * 这种SQL。SELECT * 这种SQL,走索引无法避免回表,走HASH连接的时候会将驱动表所有的列放入PGA中,浪费PGA内存。执行计划中(V$SQL_PLAN/PLAN_TABLE),projection字段表示访问了哪些字段,如果projection字段中字段个数等于表的字段总个数,那么我们就可以判断SQL语句使用了SELECT *。以下脚本抓出SELECT * 的SQL:

select a.sql_id, a.sql_text, c.owner, d.table_name, d.column_cnt, c.size_mb
  from v$sql a,
       v$sql_plan b,
       (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
          from dba_segments
         group by owner, segment_name) c,
       (select owner, table_name, count(*) column_cnt
          from dba_tab_cols
         group by owner, table_name) d
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and b.object_owner = c.owner
   and b.object_name = c.segment_name
   and b.object_owner = d.owner
   and b.object_name = d.table_name
   and REGEXP_COUNT(b.projection, ']') = d.column_cnt
   and c.owner = 'SCOTT'
 order by 6 desc;

我们在Scott账户中运行如下SQL:

select * from t where object_id<1000;

我们使用脚本将其抓出:

select a.sql_id, a.sql_text, c.owner, d.table_name, d.column_cnt, c.size_mb
from v$sql a,
     v$sql_plan b,
     (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
      from dba_segments
      group by owner, segment_name) c,
     (select owner, table_name, count(*) column_cnt
      from dba_tab_cols
      group by owner, table_name) d
where a.sql_id = b.sql_id
  and a.child_number = b.child_number
  and b.object_owner = c.owner
  and b.object_name = c.segment_name
  and b.object_owner = d.owner
  and b.object_name = d.table_name
  and REGEXP_COUNT(b.projection, ']') = d.column_cnt
  and c.owner = 'SCOTT'
order by 6 desc;

在这里插入图片描述

5、抓出有标量子查询的SQL

此脚本不依赖统计信息。在开发过程中,我们应该尽量避免编写标量子查询。我们可以通过分析执行计划,抓出标量子查询语句。同一个SQL语句,执行计划中如果有两个或者两个以上的depth=1的执行计划就表示SQL中出现了标量子查询。以下脚本抓出Scott账户下在SQL*Plus中运行过的标量子查询语句:

select sql_id, sql_text, module
  from v$sql
 where parsing_schema_name = 'SCOTT'
   and module = 'SQL*Plus'
   AND sql_id in
       (select sql_id
          from (select sql_id,
                     count(*) over(partition by sql_id, child_number, depth) cnt
                  from V$SQL_PLAN
                 where depth = 1
                   and (object_owner = 'SCOTT' or object_owner is null))
         where cnt >= 2);

我们在SQL*Plus中运行如下标量子查询语句:

SQL> select dname,
  2   (select max(sal) from emp where deptno = d.deptno) max_sal
  3  from dept d;

DNAME             MAX_SAL
-------------- ----------
ACCOUNTING           5000
RESEARCH             3000
SALES                2850
OPERATIONS

我们利用以上脚本将刚运行过的标量子查询抓出:

SQL> select sql_id, sql_text, module
  2    from v$sql
  3   where parsing_schema_name = 'SCOTT'
  4     and module = 'SQL*Plus'
  5     AND sql_id in
  6         (select sql_id
  7            from (select sql_id,
  8                       count(*) over(partition by sql_id, child_number, depth) cnt
  9                    from V$SQL_PLAN
 10                   where depth = 1
 11                     and (object_owner = 'SCOTT' or object_owner is null))
 12           where cnt >= 2);

SQL_ID          SQL_TEXT                                        MODULE
--------------- ----------------------------------------------  ---------------------
739fhcu0pbz28   select dname,  (select max(sal) from emp where  SQL*Plus
                 deptno = d.deptno) max_sal from dept d

6、抓出带有自定义函数的SQL

此脚本不依赖统计信息。在开发过程中,我们应该避免在SQL语句中调用自定义函数。我们可以通过以下SQL语句抓出SQL语句中调用了自定义函数的SQL:

select distinct sql_id, sql_text, module
  from V$SQL,
       (select object_name
          from DBA_OBJECTS O
         where owner = 'SCOTT'
           and object_type in ('FUNCTION', 'PACKAGE'))
 where (instr(upper(sql_text), object_name) > 0)
   and plsql_exec_time > 0
   and regexp_like(upper(sql_fulltext), '^[SELECT]')
   and parsing_schema_name = 'SCOTT';

我们在Scott账户中创建如下函数:

create or replace function f_getdname(v_deptno in number) return varchar2 as
  v_dname dept.dname%type;
begin
  select dname into v_dname from dept where deptno = v_deptno;
  return v_dname;
end f_getdname;
/

然后我们在Scott账户中运行如下SQL:

SQL> select empno,sal,f_getdname(deptno) dname from emp;

     EMPNO        SAL DNAME
---------- ---------- -------------------------
      7369        800 RESEARCH
      7499       1600 SALES
      7521       1250 SALES
      7566       2975 RESEARCH
      7654       1250 SALES
      7698       2850 SALES
      7782       2450 ACCOUNTING
      7788       3000 RESEARCH
      7839       5000 ACCOUNTING
      7844       1500 SALES
      7876       1100 RESEARCH
      7900        950 SALES
      7902       3000 RESEARCH
      7934       1300 ACCOUNTING

我们通过脚本抓出刚执行过的SQL语句:

SQL> select distinct sql_id, sql_text, module
  2    from V$SQL,
  3         (select object_name
  4            from DBA_OBJECTS O
  5           where owner = 'SCOTT'
  6             and object_type in ('FUNCTION', 'PACKAGE'))
  7   where (instr(upper(sql_text), object_name) > 0)
  8     and plsql_exec_time > 0
  9     and regexp_like(upper(sql_fulltext), '^[SELECT]')
 10     and parsing_schema_name = 'SCOTT';

SQL_ID          SQL_TEXT                                                MODULE
--------------- ------------------------------------------------------- ---------
2ck71xc69j49u   select empno,sal,f_getdname(deptno) dname from emp      SQL*Plus

7、抓出表被多次反复调用SQL

此脚本不依赖统计信息。在开发过程中,我们应该避免在同一个SQL语句中对同一个表多次访问。我们可以通过下面SQL抓出同一个SQL语句中对某个表进行多次扫描的SQL:

select a.parsing_schema_name schema,
       a.sql_id,
       a.sql_text,
       b.object_name,
       b.cnt
  from v$sql a,
       (select *
          from (select sql_id,
                       child_number,
                       object_owner,
                       object_name,
                       object_type,
                       count(*) cnt
                  from v$sql_plan
                 where object_owner = 'SCOTT'
                 group by sql_id,
                          child_number,
                          object_owner,
                          object_name,
                          object_type)
         where cnt >= 2) b
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number;

我们在Scott账户中运行如下SQL:

select ename,job,deptno from emp where sal>(select avg(sal) from emp);

以上SQL访问了emp表两次,我们可以通过脚本将其抓出:

SQL> select a.parsing_schema_name schema,
  2         a.sql_id,
  3         a.sql_text,
  4         b.object_name,
  5         b.cnt
  6    from v$sql a,
  7         (select *
  8            from (select sql_id,
  9                         child_number,
 10                         object_owner,
 11                         object_name,
 12                         object_type,
 13                         count(*) cnt
 14                    from v$sql_plan
 15                   where object_owner = 'SCOTT'
 16                   group by sql_id,
 17                            child_number,
 18                            object_owner,
 19                            object_name,
 20                            object_type)
 21           where cnt >= 2) b
 22   where a.sql_id = b.sql_id
 23     and a.child_number = b.child_number;

SCHEMA          SQL_ID          SQL_TEXT                      OBJECT_NAME         CNT
--------------- --------------- ----------------------------- ------------ ----------
SCOTT           fdt0z70z43vgv   select ename,job,deptno from  EMP                  2
                                emp where sal>(select avg(sal)
                                 from emp)

8、抓出走了FILTER的SQL

此脚本不依赖统计信息。当where子查询没能unnest,执行计划中就会出现FILTER,对于此类SQL,我们应该在上线之前对其进行改写,避免执行计划中出现FILTER,以下脚本可以抓出where子查询没能unnest的SQL:

select parsing_schema_name schema, sql_id, sql_text
  from v$sql
 where parsing_schema_name = 'SCOTT'
   and (sql_id, child_number) in
       (select sql_id, child_number
          from v$sql_plan
         where operation = 'FILTER'
           and filter_predicates like '%IS NOT NULL%'
        minus
        select sql_id, child_number
          from v$sql_plan
         where object_owner = 'SYS');

9、抓出返回行数较多的嵌套循环SQL

此脚本不依赖统计信息。两表关联返回少量数据应该走嵌套循环,如果返回大量数据,应该走HASH连接,或者是排序合并连接。如果一个SQL语句返回行数较多(大于1万行),SQL的执行计划在最后几步(Id<=5)走了嵌套循环,我们可以判定该执行计划中的嵌套循环是有问题的,应该走HASH连接。以下脚本抓出返回行数较多的嵌套循环SQL:

select *
  from (select parsing_schema_name schema,
               sql_id,
               sql_text,
               rows_processed / executions rows_processed
          from v$sql
         where parsing_schema_name = 'SCOTT'
           and executions > 0
           and rows_processed / executions > 10000
         order by 4 desc) a
 where a.sql_id in (select sql_id
                      from v$sql_plan
                     where operation like '%NESTED LOOPS%'
                       and id <= 5);

10、抓出NL被驱动表走了全表扫描的SQL

此脚本不依赖统计信息。嵌套循环的被驱动表应该走索引,以下脚本抓出嵌套循环被驱动表走了全表扫描的SQL,同时根据表大小降序显示:

select c.sql_text, a.sql_id, b.object_name, d.mb
  from v$sql_plan a,
       (select *
          from (select sql_id,
                       child_number,
                       object_owner,
                       object_name,
                       parent_id,
                       operation,
                       options,
                       row_number() over(partition by sql_id, child_number, parent_id order by id) rn
                  from v$sql_plan)
         where rn = 2) b,
       v$sql c,
       (select owner, segment_name, sum(bytes / 1024 / 1024) mb
          from dba_segments
         group by owner, segment_name) d
 where b.sql_id = c.sql_id
   and b.child_number = c.child_number
   and b.object_owner = 'SCOTT'
   and a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and a.operation like '%NESTED LOOPS%'
   and a.id = b.parent_id
   and b.operation = 'TABLE ACCESS'
   and b.options = 'FULL'
   and b.object_owner = d.owner
   and b.object_name = d.segment_name
 order by 4 desc;

11、抓出走了TABLE ACCESS FULL的SQL

此脚本不依赖统计信息。如果一个大表走了全表扫描,会严重影响SQL性能。这时我们可以查看大表与谁进行关联。如果大表与小表(小结果集)关联,我们可以考虑让大表作为嵌套循环被驱动表,大表走连接列索引。如果大表与大表(大结果集)关联,我们可以检查大表过滤条件是否可以走索引,也要检查大表被访问了多少个字段。假设大表有50个字段,但是只访问了其中5个字段,这时我们可以建立一个组合索引,将where过滤字段、表连接字段以及select访问的字段组合在一起,这样就可以直接从索引中获取数据,避免大表全表扫描,从而提升性能。下面脚本抓出走了全表扫描的SQL,同时显示访问了表多少个字段,表一共有多少个字段以及表段大小:

select a.sql_id,
       a.sql_text,
       d.table_name,
       REGEXP_COUNT(b.projection, ']') ||'/'|| d.column_cnt  column_cnt,
       c.size_mb,
       b.FILTER_PREDICATES filter
  from v$sql a,
       v$sql_plan b,
       (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
          from dba_segments
         group by owner, segment_name) c,
       (select owner, table_name, count(*) column_cnt
          from dba_tab_cols
         group by owner, table_name) d
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and b.object_owner = c.owner
   and b.object_name = c.segment_name
   and b.object_owner = d.owner
   and b.object_name = d.table_name
   and c.owner = 'SCOTT'
   and b.operation = 'TABLE ACCESS'
   and b.options = 'FULL'
 order by 5 desc;

12、抓出走了INDEX FULL SCAN的SQL

此脚本不依赖统计信息。INDEX FULL SCAN会扫描索引中所有的叶子块,单块读。如果索引很大,执行计划中出现了INDEX FULL SCAN,这时SQL会出现严重的性能问题,因此我们需要抓出走了INDEX FULL SCAN的SQL。以下脚本抓出走了INDEX FULL SCAN的SQL并且根据索引段大小降序显示:

select c.sql_text, c.sql_id, b.object_name, d.mb
  from v$sql_plan b,
       v$sql c,
       (select owner, segment_name, sum(bytes / 1024 / 1024) mb
          from dba_segments
         group by owner, segment_name) d
 where b.sql_id = c.sql_id
   and b.child_number = c.child_number
   and b.object_owner = 'SCOTT'
   and b.operation = 'INDEX'
   and b.options = 'FULL SCAN'
   and b.object_owner = d.owner
   and b.object_name = d.segment_name
 order by 4 desc;

13、抓出走了INDEX SKIP SCAN的SQL

此脚本不依赖统计信息。当执行计划中出现了INDEX SKIP SCAN,通常说明需要额外添加一个索引。以下脚本抓出走了INDEX SKIP SCAN的SQL:

select c.sql_text, c.sql_id, b.object_name, d.mb
  from v$sql_plan b,
       v$sql c,
       (select owner, segment_name, sum(bytes / 1024 / 1024) mb
          from dba_segments
         group by owner, segment_name) d
 where b.sql_id = c.sql_id
   and b.child_number = c.child_number
   and b.object_owner = 'SCOTT'
   and b.operation = 'INDEX'
   and b.options = 'SKIP SCAN'
   and b.object_owner = d.owner
   and b.object_name = d.segment_name
 order by 4 desc;

14、抓出索引被哪些SQL引用

此脚本不依赖统计信息。有时开发人员可能会胡乱建立一些索引,但是这些索引在数据库中可能并不会被任何一个SQL使用。这样的索引会增加维护成本,我们可以将其删掉。下面脚本查询SQL使用哪些索引:

select a.sql_text, a.sql_id, b.object_owner, b.object_name, b.object_type
  from v$sql a, v$sql_plan b
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and object_owner = 'SCOTT'
   and object_type like '%INDEX%'
order by 3,4,5;

15、 抓出走了笛卡儿积的SQL

当两表没有关联条件的时候就会走笛卡儿积,当Rows被估算为1的时候,也可能走笛卡儿积连接。下面脚本抓出走了笛卡儿积的SQL:

select c.sql_text,
       a.sql_id,
       b.object_name,
       a.filter_predicates filter,
       a.access_predicates predicate,
       d.mb
  from v$sql_plan a,
       (select *
          from (select sql_id,
                       child_number,
                       object_owner,
                       object_name,
                       parent_id,
                       operation,
                       options,
                       row_number() over(partition by sql_id, child_number, parent_id order by id) rn
                  from v$sql_plan)
         where rn = 1) b,
       v$sql c,
       (select owner, segment_name, sum(bytes / 1024 / 1024) mb
          from dba_segments
         group by owner, segment_name) d
 where b.sql_id = c.sql_id
   and b.child_number = c.child_number
   and b.object_owner = 'SCOTT'
   and a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and a.operation = 'MERGE JOIN'
   and a.id = b.parent_id
   and a.options = 'CARTESIAN'
   and b.object_owner = d.owner
   and b.object_name = d.segment_name
 order by 4 desc;

16、抓出走了错误的排序合并连接的SQL

此脚本不依赖统计信息。排序合并连接一般用于非等值关联,如果两表是等值关联,我们建议使用HASH连接代替排序合并连接,因为HASH连接只需要将驱动表放入PGA中,而排序合并连接要么是将两个表放入PGA中,要么是将一个表放入PGA中、另外一个表走INDEX FULL SCAN,然后回表。如果两表是等值关联并且两表比较大,这时应该走HASH连接而不是排序合并连接。下面脚本抓出两表等值关联但是走了排序合并连接的SQL,同时显示离MERGE JOIN关键字较远的表的段大小(太大PGA放不下):

select c.sql_id, c.sql_text, d.owner, d.segment_name, d.mb
  from v$sql_plan a,
       v$sql_plan b,
       v$sql c,
       (select owner, segment_name, sum(bytes / 1024 / 1024) mb
          from dba_segments
         group by owner, segment_name) d
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and b.operation = 'SORT'
   and b.options = 'JOIN'
   and b.access_predicates like '%"="%'
   and a.parent_id = b.id
   and a.object_owner = 'SCOTT'
   and b.sql_id = c.sql_id
   and b.child_number = c.child_number
   and a.object_owner = d.owner
   and a.object_name = d.segment_name
 order by 4 desc;

17、抓出LOOP套LOOP的PSQL

此脚本不依赖统计信息。在编写PLSQL的时候,我们应该尽量避免LOOP套LOOP,因为双层循环,最内层循环类似笛卡儿积。假设外层循环返回1 000行数据,内层循环返回1 000行数据,那么内层循环里面的代码就会执行1000*1000次。以下脚本可以抓出LOOP套LOOP的PLSQL:

with x as
(select / *+ materialize */ owner,name,type,line,text,rownum rn from dba_source where (upper(text) like '%END%LOOP%' or upper(text) like '%FOR%LOOP%'))
select a.owner,a.name,a.type from x a,x b
where ((upper(a.text) like '%END%LOOP%'
and upper(b.text) like '%END%LOOP%'
and a.rn+1=b.rn)
or (upper(a.text) like '%FOR%LOOP%'
and upper(b.text) like '%FOR%LOOP%'
and a.rn+1=b.rn))
and a.owner=b.owner
and a.name=b.name
and a.type=b.type
and a.owner='SCOTT';

18、抓出走了低选择性索引的SQL

此脚本依赖统计信息。如果一个索引选择性很低,说明列数据分布不均衡。当SQL走了数据分布不均衡列的索引,很容易走错执行计划,此时我们应该检查SQL语句中是否有其他过滤条件,如果有其他过滤条件,可以考虑建立组合索引,将选择性高的列作为引导列;如果没有其他过滤条件,应该检查列是否有收集直方图。以下脚本抓出走了低选择性索引的SQL:

select c.sql_id,
       c.sql_text,
       b.index_name,
       e.table_name,
       trunc(d.num_distinct / e.num_rows * 100, 2) selectivity,
       d.num_distinct,
       e.num_rows
  from v$sql_plan a,
       (select *
          from (select index_owner,
                       index_name,
                       table_owner,
                       table_name,
                       column_name,
                       count(*) over(partition by index_owner, index_name, table_owner, table_name) cnt
                  from dba_ind_columns)
         where cnt = 1) b,
       v$sql c,
       dba_tab_col_statistics d,
       dba_tables e
 where a.object_owner = b.index_owner
   and a.object_name = b.index_name
   and b.index_owner = 'SCOTT'
   and a.access_predicates is not null
   and a.sql_id = c.sql_id
   and a.child_number = c.child_number
   and d.owner = e.owner
   and d.table_name = e.table_name
   and b.table_owner = e.owner
   and b.table_name = e.table_name
   and d.column_name = b.column_name
   and d.table_name = b.table_name
   and d.num_distinct / e.num_rows < 0.1;

19、抓出可以创建组合索引的SQL(回表再过滤选择性高的列)

回表次数太多会严重影响SQL性能。当执行计划中发生了回表再过滤并且过滤字段的选择性比较高,我们可以将过滤字段包含在索引中避免回表再过滤,从而减少回表次数,提升查询性能。以下脚本抓出回表再过滤选择性较高的列:

select a.sql_id,
       a.sql_text,
       f.table_name,
       c.size_mb,
       e.column_name,
       round(e.num_distinct / f.num_rows * 100, 2) selectivity
  from v$sql a,
       v$sql_plan b,
       (select owner, segment_name, sum(bytes / 1024 / 1024) size_mb
          from dba_segments
         group by owner, segment_name) c,
       dba_tab_col_statistics e,
       dba_tables f
 where a.sql_id = b.sql_id
   and a.child_number = b.child_number
   and b.object_owner = c.owner
   and b.object_name = c.segment_name
   and e.owner = f.owner
   and e.table_name = f.table_name
   and b.object_owner = f.owner
   and b.object_name = f.table_name
   and instr(b.filter_predicates, e.column_name) > 0
   and (e.num_distinct / f.num_rows) > 0.1
   and c.owner = 'SCOTT'
   and b.operation = 'TABLE ACCESS'
   and b.options = 'BY INDEX ROWID'
   and e.owner = 'SCOTT'
 order by 4 desc;

20、抓出可以创建组合索引的SQL(回表只访问少数字段)

此脚本不依赖统计信息。我们在第1章中讲到,回表次数太多会严重影响SQL性能。当SQL走索引回表只访问表中少部分字段,我们可以将这些字段与过滤条件组合起来建立为一个组合索引,这样就能避免回表,从而提升查询性能。下面脚本抓出回表只访问少数字段的SQL:

select a.sql_id,a.sql_text,d.table_name,REGEXP_COUNT(b.projection, ']') ||'/'|| d.column_cntcolumn_cnt,c.size_mb,...