【问题标题】:Strange speed changes with sql querysql查询的奇怪速度变化
【发布时间】:2011-03-26 10:33:51
【问题描述】:

我无法为我正在寻找的内容获取正确的查询 (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

解释:

|身份证 |操作 |姓名 |行 |字节 |成本 | | 0 |选择声明 | | 1 | 14 | 3080| | 1 |排序聚合 | | 1 | 14 | | | 2 |按索引 ROWID 访问表| CLAIM_KEY | 6531 | 91434 | 3080| | 3 |索引跳过扫描 | 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

解释:

|身份证 |操作 |姓名 |行 |字节 |成本 | | 0 |选择声明 | | 1 | 11 | 9195 | | 1 |排序聚合 | | 1 | 11 | | | 2 |表访问完全 | 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))
|身份证 |操作 |姓名 |行 |字节 |成本 | | 0 |选择声明 | | 1 | 8 | 4 | | 1 |排序聚合 | | 1 | 8 | | | 2 |索引快速全扫描| 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) */,它的执行时间会下降,并生成以下计划。

|身份证 |操作 |姓名 |行 |字节 |成本 | | 0 |选择声明 | | 1 | 14 | 9195 | | 1 |排序聚合 | | 1 | 14 | | | 2 |表访问完全 | CLAIM_KEY | 6531 | 91434 | 9195 |

【问题讨论】:

  • Oracle 查询性能问题的标准注释:如果您可以发布上述每个查询的执行计划,人们将获得更多有用的信息。

标签: sql oracle


【解决方案1】:

为了更好地了解发生了什么,试试这个:

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 视为一个表。您在查询中需要的所有内容都可以仅通过索引来回答;不需要表访问权限。 (我猜 I_CLAIM_KEY_002 被定义为 clnt_id、dte_of_srvce 并且 clnt_id 或 dte_of_srvce 不能为空。由于 ck.id 应该是一个非空属性,所以对 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。如果是,那么你需要找出原因。:)

无论如何...我希望这很有意义..我的意思是。

【讨论】:

  • 嘿,在运行第一个查询的后半部分时,“=”下出现“缺少右括号”错误?我不熟悉你的语法,你知道为什么吗?
  • AFAIK 您不能在 SQL 查询中使用命名参数表示法。试试select * from table(dbms_xplan.display('PLAN_TABLE','query1'))
  • dbms_xplan 的上述语法实际上是在 11g 上测试的,所以如果它不向后兼容,我深表歉意。 @Dave Costa 建议的语法可能是 10g 的方式。
  • 嘿,谢谢。我以前从未使用过解释。我已经包含了结果,你介意解释一下吗? pastebin.com/1PacyWAb
  • 代替评论中的链接,您能否使用解释计划更新您的问题,针对每个查询?哦,在 SO 中格式化为代码。
【解决方案2】:

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

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;

我不能在家里尝试这种东西,所以它可能不好,但它可能会有所帮助。

【讨论】:

  • 其实我已经试过了。结果一模一样,仍然需要大约 10 分钟才能运行。
  • 什么是 CLAIM_KEY 和 I_CLAIM_KEY_001?
【解决方案3】:

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

【讨论】:

  • 是的,它们是可重复的。抱歉,我不明白你所说的块缓存是什么意思?
  • 数据库缓存了您第一次查询的结果。
  • 哦,好的。不,绝对不是那样。
【解决方案4】:

可能是因为 cInt_id 仅在您的第三个索引上。否则它将使用第二个

【讨论】:

    【解决方案5】:

    在我看来,您帖子中显示的所有索引都不是很有用。我希望它无论如何都会进行全表扫描。

    如果可以的话,添加一个索引

    DTE_OF_SRVCE, CLM_TYPE, PRGRM_ID
    

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

    分享和享受。

    【讨论】:

    • 感谢您的建议。但似乎我无权创建索引。
    猜你喜欢
    • 1970-01-01
    • 2022-11-11
    • 2016-11-22
    • 2022-01-02
    • 2016-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-19
    相关资源
    最近更新 更多