奇怪的速度随着sql查询而变化

时间:2010-08-06 16:23:53

标签: sql oracle

我无法为我正在寻找的内容获取正确的查询(oracle)。 基本上我想要的是:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1

解释:

| Id  | Operation                    |  Name            | Rows  | Bytes | Cost |
|   0 | SELECT STATEMENT             |                  |     1 |    14 |  3080|
|   1 |  SORT AGGREGATE              |                  |     1 |    14 |      |
|   2 |   TABLE ACCESS BY INDEX ROWID| CLAIM_KEY        |  6531 | 91434 |  3080|
|   3 |    INDEX SKIP SCAN           | I_CLAIM_KEY_001  |  1306K|       |  2813|

这个查询得到了我想要的东西(结果平均为20),但需要10分钟才能运行。

以下查询不完整,但运行速度更快:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5

说明:

| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT     |             |     1 |    11 |  9195 |
|   1 |  SORT AGGREGATE      |             |     1 |    11 |       |
|   2 |   TABLE ACCESS FULL  | CLAIM_KEY   | 19592 |   210K|  9195 |

这也返回大约20,虽然这只是侥幸,我不能依赖它,我需要包括prgrm_id。事情是它只需要20秒。

以下查询不是我正在寻找的,而是提供了对性能的了解:

SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
| Id  | Operation             |  Name            | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT      |                  |     1 |     8 |     4 |
|   1 |  SORT AGGREGATE       |                  |     1 |     8 |       |
|   2 |   INDEX FAST FULL SCAN| I_CLAIM_KEY_002  |   195K|  1530K|     4 |

这也需要20秒,但它平均返回700条记录。表claim_key大约有2500万行。

此表上有多个索引。他们是:

IX_CLAIM_KEY_CREATED: CREATED_ON
I_CLAIM_KEY_001: CLNC_STE_ID, PRVDR_ID, PRGRM_ID, UPDATED_ON
I_CLAIM_KEY_002: SRCE_ID, PRVDR_ID, CLNC_ID, DTE_OF_SRVCE, PRGRM_ID
I_CLAIM_KEY_003: CLNT_ID, DTE_OF_SRVCE
I_CLAIM_KEY_004: TRNSMSN_ID, CLM_STTS
I_CLAIM_KEY_005: UPDATED_ON
I_CLAIM_KEY_006: PRVDR_ID, CMN_SRCE_ID
PK_CLAIM_ID: ID

我想知道的是为什么添加prgrm_id会让它减慢这么多?我本来希望它很快,因为它只需搜索(ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))指定的700行。这是一个不正确的假设吗?

修改

在第一个查询中使用提示/*+ FULL(ck) */,其执行时间会下降并生成以下计划。

| Id  | Operation            |  Name       | Rows  | Bytes | Cost  |
|   0 | SELECT STATEMENT     |             |     1 |    14 |  9195 |
|   1 |  SORT AGGREGATE      |             |     1 |    14 |       |
|   2 |   TABLE ACCESS FULL  | CLAIM_KEY   |  6531 | 91434 |  9195 |

5 个答案:

答案 0 :(得分:4)

为了更好地了解正在发生的事情,请尝试以下方法:

explain plan set statement_id = 'query1' for
SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1;

然后:

select *
from table(dbms_xplan.display(statement_id=>'query1'));

我猜你会在claim_key上看到一条表明TABLE ACCESS FULL的行。

然后尝试:

explain plan set statement_id = 'query2' for
SELECT  count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5;

select *
from table(dbms_xplan.display(statement_id=>'query2'));

并检查它(推测)使用的索引。这应该可以让您了解数据库正在做什么,这有助于找出为什么正在执行它。


好的,根据您的解释计划,这是一个典型的例子,“索引并不总是好的,表扫描并不总是坏的。”

INDEX SKIP SCAN是数据库可以尝试使用索引的地方,即使甚至没有使用索引的前导列。基本上,如果您的索引看起来像这样(过度简化):

COL1   COL2   ROWID
A      X      1        <--
A      Y      2
A      Z      3
B      X      4        <--
B      Y      5
B      Z      6 

并且你的条件是WHERE col2 ='X'索引跳过扫描说通过COL1中的每个组合查看col2 ='X'。一旦找到匹配(例如col1 = A,col2 = X),它就会“跳过”col1中的值,直到值变化(col1 = B,然后col1 = C等),并查找更多匹配。

问题是索引(通常是!)的工作原理如下: 1)找到索引中找到值的下一个rowid 2)使用该rowid转到表块(TABLE ACCESS BY INDEX ROWID) 3)重复直到找不到更多匹配。

(对于跳过扫描,它还会产生查找前导列的下一个值更改位置的成本。)

对于少数行来说,这一切都很好,但却受到收益递减规律的影响;当你有大量的行时,它并不是那么好。那是因为它必须读取一个索引块,然后是一个表块,然后是一个索引块,一个表块(即使先前已经读过该表块)。

全表扫描只是“犁”通过数据部分归功于...多块读取。数据库可以在一次读取中从磁盘读取多个块,并且不会多次读取同一个块。

INDEX FAST FULL SCAN基本上将I_CLAIM_KEY_002视为表格。查询中所需的所有内容都可以仅通过索引来回答;不需要TABLE ACCESS。 (我猜I_CLAIM_KEY_002被定义为clnt_id,dte_of_srvce并且clnt_id或dte_of_srvce不可为空。由于ck.id应该是非null属性,ck.id的计数与ck.clnt_id的计数相同。)

至于您的初始查询,除非您想重新编写索引,请尝试以下操作:

SELECT  /*+ FULL(ck) */ count(ck.id)
FROM    claim_key ck
WHERE   (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY))
     AND ck.clm_type = 5
     AND ck.prgrm_id = 1

将强制对claim_key(ck)进行全表扫描,您可以看到与其他两个类似的性能。 (检查是否首先在查询前面添加“explain plan set statement_id ='query_hint'for”并在运行之前运行dbms_xplan查询。)

(现在你会问“我想一直提出这样的提示”吗?请不要。这只是为了测试。这只是为了检查FTS是否优于INDEX SKIP SCAN。如果是,那么你需要找出原因。:)

无论如何......我希望这样做...我的意思是感觉。

答案 1 :(得分:1)

在我看来,你的帖子中显示的所有索引都没有用。无论如何,我都希望它可以进行全表扫描。

如果可以,请在

上添加索引
DTE_OF_SRVCE, CLM_TYPE, PRGRM_ID

看看是否有帮助。如果您想尝试仅索引检索,请将ID添加到索引的末尾(因此它将是DTE_OF_SRVCE,CLM_TYPE,PRGRM_ID,ID)。

分享并享受。

答案 2 :(得分:0)

也许是因为cInt_id只在你的第三个索引上。否则它将使用第二个

答案 3 :(得分:0)

或许在这里说明显而易见,但这些结果是否可重复,或者您是否按照问题中指定的顺序尝试了一次?如果是这样,那么块缓存可以解释这些差异。

答案 4 :(得分:0)

如果尝试类似......会发生什么?

SELECT COUNT(*)
    FROM (SELECT ck.id,
                 ck.prgrm_id AS prgrm_id
              FROM claim_key ck
              WHERE (ck.dte_of_srvce > (SYSDATE - INTERVAL '30' DAY)) AND
                    ck.clm_type = 5) AS sq
    WHERE sq.prgrm_id = 1;

我不能在家里尝试这种事情,所以它可能没有用,但它可能会有所帮助。