我无法为我正在寻找的内容获取正确的查询(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 |
答案 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;
我不能在家里尝试这种事情,所以它可能没有用,但它可能会有所帮助。