Oracle函数使索引列失效的解决办法

在索引列上使用函数使得索引失效的是常见的索引失效原因之一,因此尽可能的避免在索引列上使用函数。尽管可以使用基于函数的索引来解决索引失效的问题,但如此一来带来的比如磁盘空间的占用以及列上过多的索引导致dml性能的下降。本文描述的是一个索引列上使用函数使其失效的案例。

一、数据版本与原始语句及相关信息

1.版本信息

sql> select * from v$version;                                       
                                                                    
banner                                                              
----------------------------------------------------------------    
oracle database 10g release 10.2.0.3.0 - 64bit production           
pl/sql release 10.2.0.3.0 - production                              
core    10.2.0.3.0      production                                  
tns for linux: version 10.2.0.3.0 - production                      
nlsrtl version 10.2.0.3.0 - production

 2.原始语句与其执行计划

sql> set autotrace traceonly exp;                                                                 
                                                                                                  
select acc_num,                                                                                   
	   curr_cd,                                                                                     
	   decode('20110728',                                                                           
			  (select to_char(last_day(to_date('20110728', 'yyyymmdd')),                                
							  'yyyymmdd')                                                                       
			   from   dual),                                                                            
			  0,                                                                                        
			  adj_credit_int_lv1_amt + adj_credit_int_lv2_amt -                                         
			  adj_debit_int_lv1_amt - adj_debit_int_lv2_amt) as interest                                
from   acc_pos_int_tbl acc_pos_int_tbl1                                                           
where  substr(business_date, 1, 6) = substr('20110728', 1, 6)                                     
	   and business_date <= '20110728';                                                             
                                                                                                  
execution plan                                                                                    
----------------------------------------------------------                                        
plan hash value: 3114115399                                                                       
                                                                                                  
-------------------------------------------------------------------------------------             
| id  | operation         | name            | rows  | bytes | cost (%cpu)| time     |             
-------------------------------------------------------------------------------------             
|   0 | select statement  |                 |   336k|    12m| 96399   (1)| 00:19:17 |             
|   1 |  fast dual        |                 |     1 |       |     2   (0)| 00:00:01 |             
|*  2 |  table access full| acc_pos_int_tbl |   336k|    12m| 96399   (1)| 00:19:17 |             
-------------------------------------------------------------------------------------             
                                                                                                  
predicate information (identified by operation id):                                               
---------------------------------------------------                                               
                                                                                                  
   2 - filter(substr("business_date",1,6)='201107' and                                            
              "business_date"<='20110728')

从执行计划可以看出,sql语句使用了全表扫描,而where 子句中只有唯一的一列business_date

3.表上的索引信息

sql> set autotrace off;                                                                                  
sql> set linesize 190                                                                                    
sql> @idx_info                                                                                           
enter value for owner: goex_admin                                                                        
old  10:           and owner = upper('&owner')                                                           
new  10:           and owner = upper('goex_admin')                                                       
enter value for table_name: acc_pos_int_tbl                                                              
old  11:           and a.table_name = upper('&table_name')                                               
new  11:           and a.table_name = upper('acc_pos_int_tbl')                                           
                                                                                                         
table_name         index_name               col_nam              cl_pos status   idx_typ         dscd    
------------------ ------------------------ -------------------- ------ -------- --------------- ----    
acc_pos_int_tbl    acc_pos_int_10dig_idx    sys_nc00032$              1 valid    function-based  asc     
                                                                                 normal                  
                                                                                                         
acc_pos_int_tbl    acc_pos_int_10dig_idx    business_date             2 valid    function-based  asc     
                                                                                 normal                  
                                                                                                         
acc_pos_int_tbl    acc_pos_int_10dig_idx    curr_cd                   3 valid    function-based  asc     
                                                                                 normal                  
                                                                                                         
acc_pos_int_tbl    pk_acc_pos_int_tbl       acc_num                   1 valid    normal          asc     
acc_pos_int_tbl    pk_acc_pos_int_tbl       business_date             2 valid    normal          asc

从索引的情况上来看有一个基于主键的索引包含了business_date列,而查询语句并没有走索引而是选择的全表扫描,而且预估所返回的行rows与bytes也是大的惊人,cost的值96399,接近10w。

二、分析与改造sql语句

1.原始的sql语句分析

sql语句中where子句的business_date列实现对记录过滤

business_date <= ‘20110728’条件不会限制索引的使用

substr(business_date, 1, 6) = substr(‘20110728’, 1, 6)使用了substr函数,限制了优化器选择索引

基于business_date列来建立索引函数,从已存在的索引来看,必要性不大

2.改造sql语句

substr(business_date, 1, 6) = substr(‘20110728’, 1, 6)的实质是等于当月,即限制返回的行为从2011.7.1日至2011.7.28

因此其返回的记录大于等于2011.7.1,且小于2011.7.28

做如下改造

business_date >=to_char(last_day(add_months(to_date(‘20110728′,’yyyymmdd’),-1)) + 1,’yyyymmdd’)

3.改造后的sql语句

select acc_num,                                                                
	   curr_cd,                                                                  
	   decode('20110728',                                                        
			  (select to_char(last_day(to_date('20110728', 'yyyymmdd')),             
							  'yyyymmdd')                                                    
			   from   dual),                                                         
			  0,                                                                     
			  adj_credit_int_lv1_amt + adj_credit_int_lv2_amt -                      
			  adj_debit_int_lv1_amt - adj_debit_int_lv2_amt) as interest             
from   acc_pos_int_tbl acc_pos_int_tbl1                                        
where  business_date >=                                                        
	   to_char(last_day(add_months(to_date('20110728', 'yyyymmdd'), -1)) + 1,    
			   'yyyymmdd')                                                           
	   and business_date <= '20110728';

4.改造后的执行计划

execution plan                                                                                               
----------------------------------------------------------                                                   
plan hash value: 66267922                                                                                    
                                                                                                             
--------------------------------------------------------------------------------------------------           
| id  | operation                   | name               | rows  | bytes | cost (%cpu)| time     |           
--------------------------------------------------------------------------------------------------           
|   0 | select statement            |                    |  1065k|    39m| 75043   (1)| 00:15:01 |           
|   1 |  fast dual                  |                    |     1 |       |     2   (0)| 00:00:01 |           
|   2 |  table access by index rowid| acc_pos_int_tbl    |  1065k|    39m| 75043   (1)| 00:15:01 |           
|*  3 |   index skip scan           | pk_acc_pos_int_tbl | 33730 |       | 41180   (1)| 00:08:15 |           
--------------------------------------------------------------------------------------------------           
                                                                                                             
predicate information (identified by operation id):                                                          
---------------------------------------------------                                                          
                                                                                                             
   3 - access("business_date">='20110701' and "business_date"<='20110728')                                   
       filter("business_date">='20110701' and "business_date"<='20110728')                                   

改造后可以看到sql语句的执行计划已经由原来的全表扫描改为执行index skip scan,但其cost也并没有降低多少

三、进一步分析

1.表的相关信息

sql> @tab_stat                                                                                        
enter value for input_table_name: acc_pos_int_tbl                                                     
old  11: where  table_name = upper('&input_table_name')                                               
new  11: where  table_name = upper('acc_pos_int_tbl')                                                 
enter value for input_owner: goex_admin                                                               
old  12:           and owner = upper('&input_owner')                                                  
new  12:           and owner = upper('goex_admin')                                                    
                                                                                                      
  num_rows       blks    em_blks  avg_space  chain_cnt avg_row_len avg_rows_per_block lst_anly  sta   
---------- ---------- ---------- ---------- ---------- ----------- ------------------ --------- ---   
  33659947     437206       1322        855          0          99                 77 27-sep-11 no

2.索引的相关信息

sql> @idx_stat                                                                                                       
enter value for input_table_name: acc_pos_int_tbl                                                                    
old  11: where  table_name = upper('&input_table_name')                                                              
new  11: where  table_name = upper('acc_pos_int_tbl')                                                                
enter value for input_owner: goex_admin                                                                              
old  12:           and owner = upper('&input_owner')                                                                 
new  12:           and owner = upper('goex_admin')                                                                   
                                                                                                                     
blev idx_name                          lf_blks   dst_keys   num_rows lf_per_key dat_blk_per_key   clus_fct lst_anly  
---- ------------------------------ ---------- ---------- ---------- ---------- --------------- ---------- --------- 
   3 pk_acc_pos_int_tbl                 155658   33777720   33777720          1               1   33777447 27-sep-11 
   3 acc_pos_int_10dig_idx              160247   32850596   32850596          1               1   32763921 27-sep-11

3.尝试在business_date列上创建索引

sql> create index i_acc_pos_int_tbl_bs_dt on acc_pos_int_tbl(business_date) tablespace tbs_tmp nologging;             
                                                                                                                      
index created.                                                                                                        
                                                                                                                      
sql> @idx_stat                                                                                                        
enter value for input_table_name: acc_pos_int_tbl                                                                     
old  11: where  table_name = upper('&input_table_name')                                                               
new  11: where  table_name = upper('acc_pos_int_tbl')                                                                 
enter value for input_owner: goex_admin                                                                               
old  12:           and owner = upper('&input_owner')                                                                  
new  12:           and owner = upper('goex_admin')                                                                    
                                                                                                                      
blev idx_name                          lf_blks   dst_keys   num_rows lf_per_key dat_blk_per_key   clus_fct lst_anly   
---- ------------------------------ ---------- ---------- ---------- ---------- --------------- ---------- ---------  
   2 i_acc_pos_int_tbl_bs_dt             93761        908   33659855        103             506     460007 30-sep-11  
   3 pk_acc_pos_int_tbl                 155658   33777720   33777720          1               1   33777447 27-sep-11  
   3 acc_pos_int_10dig_idx              160247   32850596   32850596          1               1   32763921 27-sep-11

建立索引后聚簇因子较小,差不多接近表上块的数量

4.使用新创建索引后的执行计划

execution plan                                                                                               
----------------------------------------------------------                                                   
plan hash value: 2183566226                                                                                  
                                                                                                             
-------------------------------------------------------------------------------------------------------      
| id  | operation                   | name                    | rows  | bytes | cost (%cpu)| time     |      
-------------------------------------------------------------------------------------------------------      
|   0 | select statement            |                         |  1065k|    39m| 17586   (1)| 00:03:32 |      
|   1 |  fast dual                  |                         |     1 |       |     2   (0)| 00:00:01 |      
|   2 |  table access by index rowid| acc_pos_int_tbl         |  1065k|    39m| 17586   (1)| 00:03:32 |      
|*  3 |   index range scan          | i_acc_pos_int_tbl_bs_dt |  1065k|       |  2984   (1)| 00:00:36 |      
-------------------------------------------------------------------------------------------------------      
                                                                                                             
predicate information (identified by operation id):                                                          
---------------------------------------------------                                                          
                                                                                                             
   3 - access("business_date">='20110701' and "business_date"<='20110728')                                   

从上面的执行计划看出,sql语句已经选择了新建的索引尽管返回的rows,bytes没有明显的变化,但cost已经少了近7倍。

以上所述是www.887551.com给大家介绍的oracle函数使索引列失效的解决办法,希望对大家有所帮助。在此也非常感谢大家对www.887551.com网站的支持!

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

相关推荐