[oracle性能优化]sql调优基础

不会根据执行计划进行sql调优的dba是不合格的,sql tuning是基本技能

0. 优化器的基本概念

     为sql语句找到最好的,执行成本最低的执行计划
     制定执行计划是以sql语句中涉及到的对象的统计信息为基础的。

1. 统计信息的介绍

    –表的统计信息(user_tables, user_tab_statistics)
   
    select num_rows, –表中的记录数
    blocks, –表中数据所占的数据块数
    empty_blocks, –表中的空块数
    avg_space, –数据块中平均的使用空间
    chain_cnt, –表中行连接和行迁移的数量
    avg_row_len, –每条记录的平均长度
    last_analyzed — 最近一次搜集统计信息的时间
    from user_tables where table_name=’new_sales’;
   
    –搜集表的统计信息
    exec dbms_stats.gather_table_stats(‘sh’,’new_sales’);
   
    –再来执行一次
    select num_rows, –表中的记录数
    blocks, –表中数据所占的数据块数
    empty_blocks, –表中的空块数
    avg_space, –数据块中平均的使用空间
    chain_cnt, –表中行连接和行迁移的数量
    avg_row_len, –每条记录的平均长度
    last_analyzed — 最近一次搜集统计信息的时间
    from user_tables where table_name=’new_sales’;
   
   
     –列的统计信息 (user_tab_columns, user_tab_col_statistics,user_tab_histograms)
     select column_name,
     num_distinct, –唯一值的个数
     low_value, –列上的最小值
     high_value, –列上的最大值
     density, –选择率因子(密度) = 1/(ndv),如果不存在柱状图的话
     num_nulls, –空值的个数
     num_buckets, –直方图的bucket个数
     histogram –直方图的类型
     from user_tab_columns
     where table_name=’new_sales’
   
     –搜集柱状图
     exec dbms_stats.gather_table_stats(‘sh’, ‘new_sales’, method_opt => ‘for all columns size 1 for columns size 254 cust_id’);
    
    
     –再来看看统计信息
     select column_name,
     num_distinct, –唯一值的个数
     low_value, –列上的最小值
     high_value, –列上的最大值
     density, –选择率因子(密度) = 1/(ndv),如果不存在柱状图的话
     num_nulls, –空值的个数
     num_buckets, –直方图的bucket个数
     histogram –直方图的类型
     from user_tab_columns
     where table_name=’new_sales’
    
     select
     column_name,
     endpoint_number,
     endpoint_value,
     from user_tab_histograms
     where table_name=’new_sales’ and column_name=’cust_id’
    
   
    扩展统计信息 (user_stat_extensions)
     select e.extension col_group, t.num_distinct, t.histogram
     from user_stat_extensions e, user_tab_col_statistics t
     where e.extension_name=t.column_name
     and t.table_name=’new_sales’;
    
     –搜集扩展统计信息
     declare
        cg_name varchar2(30);
     begin
        cg_name := dbms_stats.create_extended_stats(‘sh’,’new_sales’,'(prod_id,cust_id)’);
     end;

    select sys.dbms_stats.show_extended_stats_name(‘sh’,’new_sales’, ‘(prod_id,cust_id)’) col_group_name
     from dual;

    exec dbms_stats.gather_table_stats(‘sh’,’new_sales’, method_opt => –
     ‘for columns (prod_id,cust_id) size skewonly’);
    

2.统计信息不准确容易导致的问题

表统计信息不准确
    导致了表的访问方式出现了问题(全表扫描和使用索引)
    导致了表和表的链接方式出现问题(应该使用hash join,却是用了nest loop)
   
列统计信息不准确
    导致了访问表的方式不同(错误的索引)
    导致了表的连接方式不同(应该使用hash join , 但是使用了nest loop)

索引的统计信息不准确
    导致了访问表的方式不同(应该使用索引,但是使用了全表扫描)

+++++++++++++++++++++++++++++++++++++++++++
–当天线上表
create table sales_online
(
  prod_id      number  not null  ,      
  cust_id       number  not null,      
time_id      date  not null,        
channel_id     number  not null,      
promo_id       number  not null,      
quantity_sold  number(10,2) not null,
amount_sold    number(10,2) not null)

–历史归档表

create table sales_part (
  prod_id      number  not null  ,      
  cust_id       number  not null,      
time_id      date  not null,        
channel_id     number  not null,      
promo_id       number  not null,      
quantity_sold  number(10,2) not null,
amount_sold    number(10,2) not null)
partition by range (time_id)
(
partition part_20171218 values less than (to_date(’19-12-2017′,’dd-mm-yyyy’)),
partition part_20171219 values less than (to_date(’20-12-2017′,’dd-mm-yyyy’))
);

insert into sales_part
select prod_id,cust_id, sysdate-2,channel_id,promo_id,quantity_sold,amount_sold
from new_sales;

insert into sales_part
select prod_id,cust_id, sysdate-1,channel_id,promo_id,quantity_sold,amount_sold
from new_sales;

commit;

create index sales_cust_idx on sales_online(cust_id);
create index sales_part_cust_idx on sales_part(cust_id);

–每天晚上把当天数据归档之后,再删除
declare
    v_sql varchar2(3000);
begin

   v_sql := ‘alter table sales_part drop partition part_20171219’;
    execute immediate v_sql;
   
    v_sql := ‘alter table sales_part add partition part_’||to_char(sysdate+1,’yyyymmdd’)||
    ‘ values less than (to_date(‘||””||to_char(sysdate+1,’dd-mm-yyyy’)||””||’,’||””||’dd-mm-yyyy’||
    ””||’))’;
    dbms_output.put_line(v_sql);
    execute immediate v_sql;
   
    v_sql := ‘alter table sales_part exchange partition part_’||to_char(sysdate+1,’yyyymmdd’)||
    ‘ with table sales_online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
    v_sql := ‘truncate table sales_online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
    v_sql := ‘alter index sales_part_cust_idx rebuild online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
     v_sql := ‘alter index sales_cust_idx rebuild online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
  
    dbms_stats.gather_table_stats(‘sh’, ‘sales_part’);
    dbms_stats.gather_table_stats(‘sh’, ‘sales_online’);
   
end;

— 检查统计信息

    select column_name,
     num_distinct, –唯一值的个数
     low_value, –列上的最小值
     high_value, –列上的最大值
     density, –选择率因子(密度) = 1/(ndv),如果不存在柱状图的话
     num_nulls, –空值的个数
     num_buckets, –直方图的bucket个数
     histogram –直方图的类型
     from user_tab_columns
     where table_name=’sales_online’

   select num_rows, –表中的记录数
    blocks, –表中数据所占的数据块数
    empty_blocks, –表中的空块数
    avg_space, –数据块中平均的使用空间
    chain_cnt, –表中行连接和行迁移的数量
    avg_row_len, –每条记录的平均长度
    last_analyzed — 最近一次搜集统计信息的时间
    from user_tables where table_name=’sales_online’;

   

====实例1

–进行查询
select c.cust_city, sum(amount_sold) from sales_part s, new_customers c
where s.cust_id = c.cust_id
and s.cust_id > 100
and time_id between to_date(‘2017-12-18 00:00:00′ ,’yyyy-mm-dd hh24:mi:ss’) and 
to_date(‘2017-12-18 01:00:00’, ‘yyyy-mm-dd hh24:mi:ss’)
group by c.cust_city

———————————————————————————————————-
|   0 | select statement         |               |     1 |    48 |  1779   (1)| 00:00:22 | |        |
|   1 |  hash group by           |               |     1 |    48 |  1779   (1)| 00:00:22 | |        |
|*  2 |   hash join              |               |     1 |    48 |  1778   (1)| 00:00:22 | |        |
|   3 |    partition range single|               |     1 |    18 |  1373   (1)| 00:00:17 |     2 |     2 |
|*  4 |     table access full    | sales_part    |     1 |    18 |  1373   (1)| 00:00:17 |     2 |     2 |
|*  5 |    table access full     | new_customers | 54144 |  1586k|   405   (1)| 00:00:05 | |        |
——————————————————————————————-

select c.cust_city, sum(amount_sold) from sales_online s, new_customers c
where s.cust_id = c.cust_id
and s.cust_id > 100
and time_id between to_date(‘2017-12-20 00:00:00′ ,’yyyy-mm-dd hh24:mi:ss’) and 
to_date(‘2017-12-20 01:00:00’, ‘yyyy-mm-dd hh24:mi:ss’)
group by c.cust_city

————————————————————————————————
| id  | operation                     | name           | rows  | bytes | cost (%cpu)| time     |
————————————————————————————————
|   0 | select statement              |                |     1 |    65 |   406   (1)| 00:00:05 |
|   1 |  hash group by                |                |     1 |    65 |   406   (1)| 00:00:05 |
|*  2 |   hash join                   |                |     1 |    65 |   405   (1)| 00:00:05 |
|*  3 |    table access by index rowid| sales_online   |     1 |    35 |     0   (0)| 00:00:01 |
|*  4 |     index range scan          | sales_cust_idx |     1 |       |     0   (0)| 00:00:01 |
|*  5 |    table access full          | new_customers  | 54144 |  1586k|   405   (1)| 00:00:05 |
————————————————————————————————
–向表sales_online 中插入一些数据

insert into sales_online
select prod_id,cust_id, sysdate,channel_id,promo_id,quantity_sold,amount_sold
from new_sales;

commit;

–再次查询数据
select c.cust_city, sum(amount_sold) from sales_online s, new_customers c
where s.cust_id = c.cust_id
and s.cust_id > 100
and time_id between to_date(‘2017-12-20 00:00:00′ ,’yyyy-mm-dd hh24:mi:ss’) and 
to_date(‘2017-12-20 01:00:00’, ‘yyyy-mm-dd hh24:mi:ss’)
group by c.cust_city

————————————————————————————————
|   0 | select statement              |                |     1 |    65 |   406   (1)| 00:00:05 |
|   1 |  hash group by                |                |     1 |    65 |   406   (1)| 00:00:05 |
|*  2 |   hash join                   |                |     1 |    65 |   405   (1)| 00:00:05 |
|*  3 |    table access by index rowid| sales_online   |     1 |    35 |     0   (0)| 00:00:01 |
|*  4 |     index range scan          | sales_cust_idx |     1 |       |     0   (0)| 00:00:01 |
|*  5 |    table access full          | new_customers  | 54144 |  1586k|   405   (1)| 00:00:05 |
————————————————————————————————

       
–手动搜集统计信息或许是一个办法

exec dbms_stats.gather_table_stats(‘sh’, ‘sales_online’, cascade => true);

————————————————————————————-
| id  | operation           | name          | rows  | bytes | cost (%cpu)| time     |
————————————————————————————-
|   0 | select statement    |               |     1 |    48 |  1641   (1)| 00:00:20 |
|   1 |  hash group by      |               |     1 |    48 |  1641   (1)| 00:00:20 |
|*  2 |   hash join         |               |     1 |    48 |  1640   (1)| 00:00:20 |
|*  3 |    table access full| sales_online  |     1 |    18 |  1235   (1)| 00:00:15 |
|*  4 |    table access full| new_customers | 54144 |  1586k|   405   (1)| 00:00:05 |
————————————————————————————-

执行计划变了过来。但是这不是一个好的办法,因为在生产时间搜集统计信息比较危险。

—-可以这样做

declare
    v_sql varchar2(3000);
begin

   v_sql := ‘alter table sales_part drop partition part_20171219’;
    execute immediate v_sql;
   
    –导出统计信息
    dbms_stats.export_table_stats(ownname =>’sh’,tabname=>’sales_online’,stattab=>’sales_online_st’,statid => ‘a2’);
   
    v_sql := ‘alter table sales_part add partition part_’||to_char(sysdate+1,’yyyymmdd’)||
    ‘ values less than (to_date(‘||””||to_char(sysdate+1,’dd-mm-yyyy’)||””||’,’||””||’dd-mm-yyyy’||
    ””||’))’;
    dbms_output.put_line(v_sql);
    execute immediate v_sql;
   
    v_sql := ‘alter table sales_part exchange partition part_’||to_char(sysdate+1,’yyyymmdd’)||
    ‘ with table sales_online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
    v_sql := ‘truncate table sales_online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
    v_sql := ‘alter index sales_part_cust_idx rebuild online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
   
     v_sql := ‘alter index sales_cust_idx rebuild online’;
    execute immediate v_sql;
    dbms_output.put_line(v_sql);
  
    dbms_stats.gather_table_stats(‘sh’, ‘sales_part’);
    –dbms_stats.gather_table_stats(‘sh’, ‘sales_online’);

   –导入统计信息
    dbms_stats.import_table_stats(ownname => ‘sh’, tabname => ‘sales_online’, stattab => ‘sales_online_st’, statid => ‘a2’, no_invalidate => true);

end;

(0)
上一篇 2022年3月22日
下一篇 2022年3月22日

相关推荐