在主键上运行Oracle SQL查询速度慢,全表扫描,为什么?

时间:2016-04-21 09:35:02

标签: sql oracle oracle11g query-performance sql-execution-plan

我遇到一段代码问题,当wrk.cre_surr_id是主键时,我无法理解为什么以下查询在工作表上进行全表扫描。两个表上的统计数据都是最新的,是两个表的索引。

 TABLE INDEXES
 WORKS

 INDEX NAME         UNIQUE  LOGGING     COLUMN NAME             ORDER
 WRK_I1             N       NO          LOGICALLY_DELETED_Y     Asc
 WRK_ICE_WRK_KEY    N       YES         ICE_WRK_KEY             Asc
 WRK_PK             Y       NO          CRE_SURR_ID             Asc
 WRK_TUNECODE_UK    Y       NO          TUNECODE                Asc

TLE_TITLE_TOKENS

INDEX NAME          UNIQUE  LOGGING     COLUMN NAME             ORDER
TTT_I1              N       YES         TOKEN_TYPE,             Asc
                                        SEARCH_TOKEN,
                                        DN_WRK_CRE_SURR_ID
TTT_TLE_FK_1        N       YES         TLE_SURR_ID

下面的问题查询。它的成本为245,876,看起来很高,它正在对WORKS表进行FULL TABLE扫描,表中有21,938,384行。它正在执行TLE_TITLE_TOKENS表的INDEX RANGE SCAN,其中包含19,923,002行。在解释计划上也是一个INLIST ITERATOR,我不知道这意味着什么,但我认为它与#34;(' E',& #39; N')"在我的SQL查询中。

 SELECT wrk.cre_surr_id 
 FROM   works wrk, 
        tle_title_tokens ttt      
 WHERE ttt.dn_wrk_cre_surr_id = wrk.cre_surr_id
 AND wrk.logically_deleted_y IS NULL   
 AND ttt.token_type in ('E','N')  
 AND  ttt.search_token LIKE 'BELIEVE'||'%'

当我打破查询并从TLE_TITLE_TOKENS表中进行简单选择时,我得到280,000条记录。

 select ttt.dn_wrk_cre_surr_id 
 from tle_title_tokens ttt
 where ttt.token_type in ('E','N') 
 and ttt.search_token LIKE 'BELIEVE'||'%'

如何阻止它在WORKS表上执行FULL TABLE扫描。我可以对查询提出一些提示,但我认为Oracle会非常聪明地知道在没有提示的情况下使用索引。

同样在TLE_TITLE_TOKENS表上,最好在SEARCH_TOKEN列上创建基于函数的索引,因为用户似乎在此字段上执行LIKE%搜索。基于功能的索引会是什么样的。

我在Oracle 11g数据库上运行。

提前感谢任何答案。

4 个答案:

答案 0 :(得分:1)

首先,使用join

重写查询
SELECT wrk.cre_surr_id 
FROM tle_title_tokens ttt JOIN
     works wrk 
     ON ttt.dn_wrk_cre_surr_id = wrk.cre_surr_id 
WHERE wrk.logically_deleted_y IS NULL  AND
      ttt.token_type in ('E', 'N')  AND
      ttt.search_token LIKE 'BELIEVE'||'%';

您应该能够使用索引加快此查询速度。目前尚不清楚最佳指数是什么。我建议使用tle_title_tokens(search_token, toekn_type, dn_wrk_cre_surr_id)works(cre_surr_id, logically_deleted_y)

另一种可能性是使用EXISTS编写查询,例如:

SELECT wrk.cre_surr_id 
FROM works wrk 
WHERE wrk.logically_deleted_y IS NULL AND
      EXISTS (SELECT 1
              FROM tle_title_tokens ttt
              WHERE ttt.dn_wrk_cre_surr_id = wrk.cre_surr_id AND
                    ttt.token_type IN ('N', 'E') AND
                    ttt.search_token LIKE 'BELIEVE'||'%'
             ) ;

对于此版本,您需要works(logically_deleted_y, cre_surr_id)tle_title_tokens(dn_wrk_cre_surr_id, token_type, search_token)上的索引。

答案 1 :(得分:0)

试试这个:

SELECT /*+ leading(ttt) */ wrk.cre_surr_id 
 FROM   works wrk, 
        tle_title_tokens ttt      
 WHERE ttt.dn_wrk_cre_surr_id = wrk.cre_surr_id
 AND wrk.logically_deleted_y IS NULL   
 AND ttt.token_type in ('E','N')  
 AND  ttt.search_token LIKE 'BELIEVE'||'%'

答案 2 :(得分:0)

LE_TITLE_TOKENS中的19,923,002行,

有多少记录有TOKEN_TYPE' E'有多少记录有' N'?还有其他TokenTypes吗?如果是,那么它们组合在一起多少?

如果E和N放在一起形成总记录的一小部分,那么检查是否为该列更新了直方图统计数据。

执行计划取决于给定过滤器的20M记录中从LE_TITLE_TOKENS中选择的记录数。

答案 3 :(得分:0)

我假设这个索引定义

 create index works_idx on works (cre_surr_id,logically_deleted_y);
 create index title_tokens_idx on  tle_title_tokens(search_token,token_type,dn_wrk_cre_surr_id); 

通常有两种可能的方案来执行连接

NESTED LOOPS 使用索引访问内部表WORKS,但是在外部表中的每一行循环中重复

HASH JOIN 使用FULL SCAN访问WORKS,但只能访问一次。

不可能说一个选项不好而另一个选项不错。

如果外部表中只有很少的行(很少的循环),嵌套循环会更好,但外部表(TOKEN)中记录数量的增加会变慢 HASH JOIN更慢,并且在某个行数处是合适的。

如何看待哪个执行计划更好? 简单强制Oracle使用提示运行两个scanarios并比较已用时间。

在您的情况下,您应该看到这两个执行计划

HASH JOIN

-----------------------------------------------------------------------------------------------
| Id  | Operation          | Name             | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |                  |   207K|    10M|       |  2439   (1)| 00:00:30 |
|*  1 |  HASH JOIN         |                  |   207K|    10M|  7488K|  2439   (1)| 00:00:30 |
|*  2 |   INDEX RANGE SCAN | TITLE_TOKENS_IDX |   207K|  5058K|       |    29   (0)| 00:00:01 |
|*  3 |   TABLE ACCESS FULL| WORKS            |   893K|    22M|       |   431   (2)| 00:00:06 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("TTT"."DN_WRK_CRE_SURR_ID"="WRK"."CRE_SURR_ID")
   2 - access("TTT"."SEARCH_TOKEN" LIKE 'BELIEVE%')
       filter("TTT"."SEARCH_TOKEN" LIKE 'BELIEVE%' AND ("TTT"."TOKEN_TYPE"='E' OR 
              "TTT"."TOKEN_TYPE"='N'))
   3 - filter("WRK"."LOGICALLY_DELETED_Y" IS NULL)

NESTED LOOPS

--------------------------------------------------------------------------------------
| Id  | Operation         | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |                  |   207K|    10M|   414K  (1)| 01:22:56 |
|   1 |  NESTED LOOPS     |                  |   207K|    10M|   414K  (1)| 01:22:56 |
|*  2 |   INDEX RANGE SCAN| TITLE_TOKENS_IDX |   207K|  5058K|    29   (0)| 00:00:01 |
|*  3 |   INDEX RANGE SCAN| WORKS_IDX        |     1 |    26 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------


Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("TTT"."SEARCH_TOKEN" LIKE 'BELIEVE%')
       filter("TTT"."SEARCH_TOKEN" LIKE 'BELIEVE%' AND 
              ("TTT"."TOKEN_TYPE"='E' OR "TTT"."TOKEN_TYPE"='N'))
   3 - access("TTT"."DN_WRK_CRE_SURR_ID"="WRK"."CRE_SURR_ID" AND 
              "WRK"."LOGICALLY_DELETED_Y" IS NULL)

我的好处是(有280K循环)散列连接(即 FULLTABLE SCAN )将会更好,但可能是你认识到应该使用嵌套循环。 在这种情况下,优化不会正确识别嵌套循环和散列连接之间的切换点。 导致这种情况的常见原因是错误或缺少系统统计信息或不正确的优化器参数。