sql调优基本步骤

对SQL调优基本步骤:
a) 捕获SQL语句
b) 产生SQL语句的执行计划;
c) 验证统计信息(SQL语句涉及到的表格是否做过分析),表格信息(结果集的记录数,索引),字段上面数据分布特点
d) 通过手工收集到的信息,形成自己理想的执行计划。
e) 如果做过分析,则重新分析相关表格或者做柱状图分析。
f) 如果没有做过分析,则通过尝试不同的Hint,从而获得合适的执行计划。
g) 当我们正常无法调优到位时,可以打开10053事件打开优化器的跟踪,看看Oracle如何选择的.
alter session set events='10053 trace name context forever,level 2';

常见的问题:
1. Statement not written for indexes 25%
2. Indexes are missing or inappropriate 16%
3. Use of single-column index merge 15%
4. Misuse of nested loop, sort merge, or hash join 12%
5. Misuse of IN, EXISTS, NOT IN, NOT EXISTS, or table joins 8%

-----------

捕获sql:

查看statistics_level参数是否开启:
show parameter statistics_level;

开启awr
alter system set statistics_level=all;

查看awr配置:
select * from dba_hist_wr_control;

修改awr配置:
execute dbms_workload_repository.modify_snapshot_settings(interval=>30,retention=>14400);
以分钟为单位

查看快照:
select * from dba_hist_snapshot;

删除快照
execute DBMS_WORKLOAD_REPOSITORY.DROP_SNAPSHOT_RANGE(low_snap_id => 1, high_snap_id => 10); 

手动生成快照:
select dbms_workload_repository.create_snapshot() from dual;
返回值是snap_id

******生成报告:
执行脚本$ORACLE_HOME/rdbms/admin,如:
sql>@?/rdbms/admin/awwrrpt.sql        --?代表ORACLE_HOME

awrrpt.sql:根据一段snapid产生一个报告
awrrpti.sql

awrsqrpt.sql:从一段snapid中产生某个sql语句的执行报告
awrsqrpi.sql

awrddrpt.sql:比较两个时间段间的差异细节
awrddrpi.sql

----------

sql执行计划:

产生执行计划:
sqlplus:
SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]]
pL/SQL developer:

查看执行计划目的:
哪个为驱动表
判断是否利用了索引
驱动表是否合适,如果不合适,对SQL语句进行更改,使优化器可以选择正确的驱动表。

计划层次关系:
PARENT1
**FIRST CHILD
****FIRST GRANDCHILD
**SECOND CHILD 

表访问方式
Full Table Scan (FTS) 全表扫描
index unique scan   --索引唯一扫描
index range scan   --索引局部扫描 (e.g. > < <> >= <= between)
index full scan   --索引全局扫描(带order by)
index fast full scan   --索引快速全局扫描,不带order by情况下常发生
index skip scan   --索引跳跃扫描,where条件列是非索引的前导列情况下常发生
Rowid 物理ID扫描

运算符:
1.sort    --排序,很消耗资源
(1)order by clauses (2)group by (3)sort merge join –-这三个会产生排序运算
2.filter    --过滤,如not in、min函数等容易产生
3.view    --视图,大都由内联视图产生(可能深入到视图基表)
4.partition view     --分区视图

------------

查看是否收集了统计信息:

select NUM_ROWS,BLOCKS,LAST_ANALYZED from dba_tables where OWNER='&OWNER' and TABLE_NAME='&TABLE_NAME';

select NUM_ROWS,BLOCKS,LAST_ANALYZED from dba_indexes where INDEX_NAME='&INDEX_NAME' and OWNER='&OWNER';

查看是否自动收集统计信息
show parameter STATISTICS_LEVEL = {ALL | TYPICAL | BASIC}
select owner,job_name,last_start_date,last_run_duration,next_run_date from dba_scheduler_jobs where job_name='GATHER_STATS_JOB';
10开始建库后默认创建gather_stats_job的定时任务,每晚22:00-06:00和周末全天开启(数据库时间)

-----------

统计信息存放:
tables
user_tab_statistics
user_tables
user_tab_partitions
user_tab_subpartitions

columns
user_tab_col_statistics
user_tab_histograms
user_part_col_statistics
user_part_histograms
user_subpart_col_statistics
user_subpart_histograms

indexes         
user_ind_statistics
user_indexes
user_ind_statistics

-----------

手动收集统计信息:
gather_table_stats
gather_index_stats
gather_schema_stats
gather_database_stats(需要很长时间)
gather_stale_stats
例如:
1) exec dbms_stats.gather_table_stats('ZQB','T');
2) execute dbms_stats.gather_schema_stats(ownname=>'ZQB',estimate_percent=>dbms_stats.auto_sample_size,method_opt=>'for all columns size auto')

--------------

自动收集统计信息:
exec dbms_scheduler.enable('SYS.GATHER_STATS_JOB');
exec dbms_scheduler.disable('SYS.GATHER_STATS_JOB');

----------

查看字段上的数据分布特点:
select cdrtype,count(*) from cardsusage200508 group by cdrtype;
CDRT   COUNT(*)
---- ----------
102         637
106     1973757
107     2390097
112       46016
113          20
对于数据分布不均匀的列要收集直方图信息

----------

查看直方图:
select ENDPOINT_NUMBER,ENDPOINT_VALUE from dba_histograms where TABLE_NAME='&TABLE_NAME' and OWNER='&OWNER' and COLUMN_NAME='&COLUMN_NAME';

-----------

收集直方图:
execute dbms_stats.gather_table_stats ('&OWNER','&TABLE_NAME',method_opt=>'for columns size auto');
column size auto让oracle自动决定哪些列需要收集直方图信息,以及桶的数据

execute dbms_stats.gather_table_stats ('&OWNER','&TABLE_NAME',method_opt=>'for columns size 10 id');
在id列上建10个桶

-----------

查看表的记录数:
全表:count
where过滤以后的记录数:select count(0) from table_name where ...

----------

分析多表连接方式:
NESTED LOOP
1) 适用于被连接的数据子集较小的情况
2) 没有索引一般不会是 nested loops,适合走hash join,因为不需要索引。
3) 如果驱动表(外循环表)返回记录太多,就不适合nested loops了。
4) 用USE_NL(table_name1 table_name2)提示来强制使用nested loop。

HASH JOIN
1) 原理:
Hash join是优化器做大数据集连接时的常用方式。优化器扫描小表(或数据源),利用连接键(也就是根据连接字段计算hash 值)在内存中建立hash表,然后扫描大表,每读到一条记录就来探测hash表一次,找出与hash表匹配的行。
2) 使用hash join时,HASH_AREA_SIZE初始化参数必须足够的大,如果是9i,Oracle建议使用SQL工作区自动管理,设置WORKAREA_SIZE_POLICY 为AUTO,然后调整PGA_AGGREGATE_TARGET即可。
3)适用:
两个巨大的表之间的连接
在一个巨大的表和一个小表之间的连接
4) 用USE_HASH(table_name1 table_name2)提示来强制使用hash join

SORT MERGE JOIN
1) 原理
Sort merge join的操作通常分三步:对连接的每个表做table access full;对table access full的结果进行排序;进行merge join对排序结果进行合并。sort merge join性能开销几乎都在前两步
2) 适用:
通常情况下hash join的效果都比sort merge join要好,然而如果行源已经被排过序,在执行sort merge join时不需要再排序了,这时sort merge join的性能会优于hash join
3) 用USE_MERGE(table_name1 table_name2)提示强制使用sort merge join

---------

尝试不同的hint,获取最优执行计划:
Hint全集

/*+ parallel(table_name,8) */并行执行,并行度<=逻辑cpu数

/*+ORDERED*/
根据表出现在FROM中的顺序,ORDERED使ORACLE依此顺序对其连接.例如:
SELECT /*+ORDERED*/ A.COL1,B.COL2,C.COL3 FROM TABLE1 A,TABLE2 B,TABLE3 C
WHERE A.COL1=B.COL1 AND B.COL1=C.COL1;

/*+USE_NL(TABLE)*/
将指定表与嵌套的连接的行源进行连接,并把指定表作为内部表.例如:
SELECT /*+ORDERED USE_NL(BSEMPMS)*/ DPTMS.DPT_NO,BSEMPMS.EMP_NO,BSEMPMS.EMP_NAM FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

/*+USE_MERGE(TABLE)*/
将指定的表与其他行源通过合并排序连接方式连接起来.例如:
SELECT /*+USE_MERGE(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

/*+USE_HASH(TABLE)*/
将指定的表与其他行源通过哈希连接方式连接起来.例如:
SELECT /*+USE_HASH(BSEMPMS,BSDPTMS)*/ * FROM BSEMPMS,BSDPTMS WHERE
BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

/*+CACHE(TABLE)*/
当进行全表扫描时,CACHE提示能够将表的检索块放置在缓冲区缓存中最近最少列表LRU的最近使用端例如:
SELECT /*+FULL(BSEMPMS) CAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;

/*+NOCACHE(TABLE)*/
当进行全表扫描时,CACHE提示能够将表的检索块放置在缓冲区缓存中最近最少列表LRU的最近使用端,例如:
SELECT /*+FULL(BSEMPMS) NOCAHE(BSEMPMS) */ EMP_NAM FROM BSEMPMS;

/*+ FULL(TABLE)*/
表明对表选择全局扫描的方法.例如:
SELECT /*+FULL(A)*/ EMP_NO,EMP_NAM FROM BSEMPMS A WHERE EMP_NO='CCBZZP';

/*+ INDEX(TABLE INDEX_NAME)*/
表明对表选择索引的扫描方法.例如:
SELECT /*+INDEX(BSEMPMS SEX_INDEX) USE SEX_INDEX BECAUSE THERE ARE FEWMALE BSEMPMS */ FROM BSEMPMS WHERE SEX='M';

/*+INDEX_ASC(TABLE INDEX_NAME)*/
表明对表选择索引升序的扫描方法.例如:
SELECT /*+INDEX_ASC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='CCBZZP';

/*+INDEX_COMBINE*/
为指定表选择位图访问路经,如果INDEX_COMBINE中没有提供作为参数的索引,将选择出位图索引的布尔组合方式.例如:
SELECT /*+INDEX_COMBINE(BSEMPMS SAL_BMI HIREDATE_BMI)*/ * FROM BSEMPMS
WHERE SAL<5000000 AND HIREDATE

/*+INDEX_JOIN(TABLE INDEX_NAME)*/
提示明确命令优化器使用索引作为访问路径.例如:
SELECT /*+INDEX_JOIN(BSEMPMS SAL_HMI HIREDATE_BMI)*/ SAL,HIREDATE
FROM BSEMPMS WHERE SAL<60000;

/*+INDEX_DESC(TABLE INDEX_NAME)*/
表明对表选择索引降序的扫描方法.例如:
SELECT /*+INDEX_DESC(BSEMPMS PK_BSEMPMS) */ FROM BSEMPMS WHERE DPT_NO='CCBZZP';

/*+INDEX_FFS(TABLE INDEX_NAME)*/
对指定的表执行快速全索引扫描,而不是全表扫描的办法.例如:
SELECT /*+INDEX_FFS(BSEMPMS IN_EMPNAM)*/ * FROM BSEMPMS WHERE DPT_NO='TEC305';

/*+ADD_EQUAL TABLE INDEX_NAM1,INDEX_NAM2,...*/
提示明确进行执行规划的选择,将几个单列索引的扫描合起来.例如:
SELECT /*+INDEX_FFS(BSEMPMS IN_DPTNO,IN_EMPNO,IN_SEX)*/ * FROM BSEMPMS WHERE 
EMP_NO='CCBZZP' AND DPT_NO='TDC306';

/*+USE_CONCAT*/
对查询中的WHERE后面的OR条件进行转换为UNION ALL的组合查询.例如:
SELECT /*+USE_CONCAT*/ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';

/*+ALL_ROWS*/
表明对语句块选择基于开销的优化方法,并获得最佳吞吐量,使资源消耗最小化.例如:
SELECT /*+ALL+_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

/*+FIRST_ROWS*/
表明对语句块选择基于开销的优化方法,并获得最佳响应时间,使资源消耗最小化.例如:
SELECT /*+FIRST_ROWS*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

/*+CHOOSE*/
表明如果数据字典中有访问表的统计信息,将基于开销的优化方法,并获得最佳的吞吐量;表明如果数据字典中没有访问表的统计信息,将基于规则开销的优化方法;例如:
SELECT /*+CHOOSE*/ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP';

/*+ RULE*/
表明对语句块选择基于规则的优化方法.例如:
SELECT /*+ RULE */ EMP_NO,EMP_NAM,DAT_IN FROM BSEMPMS WHERE EMP_NO='CCBZZP'; 

/*+ROWID(TABLE)*/
提示明确表明对指定表根据ROWID进行访问.例如:
SELECT /*+ROWID(BSEMPMS)*/ * FROM BSEMPMS WHERE ROWID>='AAAAAAAAAAAAAA'
AND EMP_NO='CCBZZP';

/*+CLUSTER(TABLE)*/ 
提示明确表明对指定表选择簇扫描的访问方法,它只对簇对象有效.例如:
SELECT /*+CLUSTER */ BSEMPMS.EMP_NO,DPT_NO FROM BSEMPMS,BSDPTMS
WHERE DPT_NO='TEC304' AND BSEMPMS.DPT_NO=BSDPTMS.DPT_NO;

/*+NO_EXPAND*/
对于WHERE后面的OR 或者IN-LIST的查询语句,NO_EXPAND将阻止其基于优化器对其进行扩展.例如:
SELECT /*+NO_EXPAND*/ * FROM BSEMPMS WHERE DPT_NO='TDC506' AND SEX='M';

/*+NOWRITE*/
禁止对查询块的查询重写操作.

/*+REWRITE*/
可以将视图作为参数.

/*+MERGE(TABLE)*/
能够对视图的各个查询进行相应的合并.例如:
SELECT /*+MERGE(V) */ A.EMP_NO,A.EMP_NAM,B.DPT_NO FROM BSEMPMS A (SELET DPT_NO
,AVG(SAL) AS AVG_SAL FROM BSEMPMS B GROUP BY DPT_NO) Va WHERE A.DPT_NO=V.DPT_NO
AND A.SAL>V.AVG_SAL;

/*+NO_MERGE(TABLE)*/
对于有可合并的视图不再合并.例如:
SELECT /*+NO_MERGE(V) */ A.EMP_NO,A.EMP_NAM,B.DPT_NO FROM BSEMPMS A (SELET DPT_NO,AVG(SAL) AS AVG_SAL FROM BSEMPMS B GROUP BY DPT_NO) V WHERE A.DPT_NO=V.DPT_NO
AND A.SAL>V.AVG_SAL;

/*+DRIVING_SITE(TABLE)*/
强制与ORACLE所选择的位置不同的表进行查询执行.例如:
SELECT /*+DRIVING_SITE(DEPT)*/ * FROM BSEMPMS,DEPT@BSDPTMS WHERE 
BSEMPMS.DPT_NO=DEPT.DPT_NO;

/*+LEADING(TABLE)*/
将指定的表作为连接次序中的首表.

/*+APPEND*/
直接插入到表的最后,可以提高速度.
insert /*+append*/ into test1 select * from test4 ;

/*+NOAPPEND*/
通过在插入语句生存期内停止并行模式来启动常规插入.
insert /*+noappend*/ into test1 select * from test4;

你可能感兴趣的:(sql调优基本步骤)