函数使得索引列失效

优化SQL查询:避免索引失效通过函数处理日期过滤条件
本文讨论了一个SQL查询中在索引列使用函数导致索引失效的问题,并提供了通过改造查询语句来解决该问题的方法。通过避免在索引列上使用函数,可以提高查询效率和性能。

      在索引列上使用函数使得索引失效的是常见的索引失效原因之一,因此尽可能的避免在索引列上使用函数。尽管可以使用基于函数的索引来
解决索引失效的问题,但如此一来带来的比如磁盘空间的占用以及列上过多的索引导致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倍。

 

MySQL 中使用函数索引时,可能会出现索引失效的情况,这通常与查询语句的写法、索引的创建方式以及优化器的行为有关。以下是函数索引失效的常见原因及其解决方案。 ### 1. 查询中使用的函数索引定义不匹配 如果在查询条件中使用的函数表达式与索引定义中的表达式不一致,则无法命中函数索引。例如,若索引是基于 `DATE_FORMAT(create_time, '%Y-%m-%d')` 创建的,而查询中使用的是 `DATE(create_time)`,则该索引不会被使用[^2]。 **解决方法:** 确保查询语句中的表达式与索引定义完全一致。例如: ```sql -- 正确使用函数索引的查询 SELECT * FROM task_queue WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2023-10-05'; ``` ### 2. 使用了不支持函数索引的 MySQL 版本 MySQL 在 8.0 版本才正式引入对函数索引的支持,在此之前版本(如 5.x)中,即使尝试创建函数索引,也不会生效。 **解决方法:** 升级到 MySQL 8.0 或更高版本以支持函数索引功能[^2]。 ### 3. 查询条件中包含运算或隐式转换 当查询条件涉及字段的运算(如加减乘除)、类型转换或与其他进行操作时,优化器可能无法使用索引,包括函数索引。 **解决方法:** 避免在索引字段上进行不必要的运算或类型转换。如有必要,应调整 SQL 逻辑,使其能直接匹配索引表达式。 ### 4. 索引选择性低或数据分布不均 如果函数处理后的值重复率高(即选择性差),优化器可能认为使用索引不如全表扫描效率高,从而放弃使用索引。 **解决方法:** 评估索引的选择性,优先为具有较高唯一性的表达式建立索引。也可以通过分析表统计信息(如 `ANALYZE TABLE`)帮助优化器做出更准确的判断。 ### 5. 查询语句本身未利用索引字段 即使存在函数索引,但如果查询语句并未引用该索引字段或其表达式,则自然无法命中索引。 **解决方法:** 检查执行计划(`EXPLAIN`),确认是否使用了预期的索引。例如: ```sql EXPLAIN SELECT * FROM task_queue WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2023-10-05'; ``` 观察输出中的 `type` 字段是否为 `ref` 或更优类型,并确保 `key` 显示正确的索引名称。 ### 6. 使用 IN 子句导致索引失效 虽然不是函数索引特有的问题,但在使用 `IN` 子句时,如果表值过多,也可能影响索引的使用效率,特别是在结合函数索引的情况下。 **解决方法:** 限制 `IN` 子句中值的数量,或将大表拆分为多个较小的子查询。同时,确保每个子查询都能有效利用索引。 ### 示例:创建和使用函数索引 ```sql -- 创建函数索引 ALTER TABLE task_queue ADD INDEX idx_func_create_time((DATE_FORMAT(create_time, '%Y-%m-%d'))); -- 查询示例 SELECT * FROM task_queue WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2023-10-05'; ``` ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清风智语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值