【问题标题】:Why is this query so slow?为什么这个查询这么慢?
【发布时间】:2011-03-11 06:46:47
【问题描述】:

我有 FOO 和 BAR 表。 FOO 有一个 BAR PK 的外键。

当我执行以下查询时,它需要几秒钟。

select foo.name, foo.description, bar.quadrant from FOO, BAR
where FOO.BAR_ID = BAR.BAR_ID

这是我的解释计划:

OPERATION        OBJECT_NAME     OPTIONS     COST 
SELECT STATEMENT                                 39 
 HASH JOIN                                       39 
  TABLE ACCESS   BAR             FULL            2 
  TABLE ACCESS   FOO             FULL            36 

FOO 中有 6000 条记录,而 BAR 只有 5 条。BAR_ID 列是 NUMBER。

这是在 Oracle 10g 上运行的,大约需要 3 秒才能完成。考虑到它执行其他查询的速度,这似乎很极端。

编辑表定义:

CREATE TABLE BAR
 (
    "BAR_ID" NUMBER NOT NULL,
    "QUADRANT" VARCHAR2(100 BYTE) NOT NULL,
    CONSTRAINT "BAR_PK" PRIMARY KEY ("BAR_ID")
 )

 CREATE TABLE FOO
 (  "FOO_ID" NUMBER NOT NULL, 
    "BAR_ID" NUMBER NOT NULL, 
    "NAME" VARCHAR2(250 BYTE) NOT NULL, 
    "DESCRIPTION" VARCHAR2(250 BYTE),
    CONSTRAINT "FOO_PK" PRIMARY KEY ("FOO_ID"), 
    CONSTRAINT "FOO__FK1" FOREIGN KEY ("BAR_ID") REFERENCES BAR ("BAR_ID") ENABLE
 );

【问题讨论】:

  • 我只是想知道您是否在表中有 FK/PK 关系,那么为什么不使用“JOIN”而不是比较选择性列呢?
  • 你能发布表格定义吗?
  • 你有关于 FOO.BAR_ID 的索引吗?
  • @Mike 是的,但解释计划保持不变,除非我使用 /*+ INDEX ... */ 提示明确强制它。使用索引对查询速度没有任何明显影响。
  • Oracle 的优化器过去非常关心from 子句中的表顺序。我不确定它是否仍然存在 - 尝试反转它们只是为了查看 - 对于哈希连接,您希望较小的表成为驱动表,而不是较大的表。

标签: sql oracle indexing


【解决方案1】:

表格是否经常更新?

foo.description 是一个巨大的 CLOB 吗?

网络延迟是否使查询看起来需要很长时间?

这些表真的是复杂的视图吗?

这些表是否曾经非常大,后来删除了大量数据?

【讨论】:

  • 1) 不,foo 每晚刷新一次,但 bar 永远不会改变 2) 不,varchar 3) 不,其他查询运行得更快,即使它们在更大的表之间进行更多连接 4) 不, 它们是表 5) 不, foo 通常每晚插入一到两次,可能还有一些更新,但大小没有太大变化
【解决方案2】:

Oracle 中内置了大量工具来调查此类问题。

从这篇论文开始:

http://method-r.com/downloads/doc_download/10-for-developers-making-friends-with-the-oracle-database-cary-millsap

【讨论】:

    【解决方案3】:

    为您的查询获取 TKPROF 跟踪以了解实际发生的情况 - explain plan 只是一个估计值。

    基本上,在查询之前执行ALTER SESSION SET SQL_TRACE = TRUE 命令,执行查询,然后再执行ALTER SESSION SET SQL_TRACE = FALSE。然后找到由USER_DUMP_DEST 参数确定的位置生成的跟踪文件(查看v$parameter 视图)。使用TKPROF 实用程序将原始跟踪文件处理为更易读的格式,并检查结果(也将其发布在此处)。

    (有关更多信息,请参阅 Oracle.com 的 Using SQL Trace and TKPROF。)

    【讨论】:

    • +1 的好建议。虽然我不会发出“alter session set sql_trace = false”,因为游标可以保持打开状态,您会错过 tkprof 文件中的行源操作。改为发出“断开连接”。
    【解决方案4】:

    据我记忆,Oracle 将其视为忽略索引的简单连接。基本思想是,因为您没有限制任一表中的数据,只是将它们连接在一起,所以它认为全表扫描会更好。如果 foo 表的 bar_id 列中有几行为 null,那么您可能需要使用索引提示。

    例如,如果您基于单个 bar_id 运行查询,解释计划可能会按预期使用索引。如果没有索引,它将对 bar 表进行全面扫描,因为它非常小,并且对 foo 表进行全面扫描,因为您没有过滤掉 bar_id 的任何值。

    最后一点是确保更新表和索引的统计信息。这对于稀疏索引很重要,因为 Oracle 可能意识到索引可以显着改变查询的成本。

    【讨论】:

      【解决方案5】:

      你确定你有很好的统计数据吗?我从你的 DDL 创建了一个测试用例,并在统计之前看到了这个计划:

      --------------------------------------------------------------------------- 
      | Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
      --------------------------------------------------------------------------- 
      |   0 | SELECT STATEMENT   |      |  4996 |  1619K|    10  (10)| 00:00:01 | 
      |*  1 |  HASH JOIN         |      |  4996 |  1619K|    10  (10)| 00:00:01 | 
      |   2 |   TABLE ACCESS FULL| BAR  |     5 |   325 |     3   (0)| 00:00:01 | 
      |   3 |   TABLE ACCESS FULL| FOO  |  4996 |  1302K|     6   (0)| 00:00:01 | 
      --------------------------------------------------------------------------- 
      

      (如果您获得 dbms_xplan 输出,您还会看到“用于此语句的动态采样”)。

      这样做之后:

      SQL> begin dbms_stats.gather_table_stats(user,'FOO'); end;
        2  /
      
      PL/SQL procedure successfully completed.
      
      SQL> c/FOO/BAR/
        1* begin dbms_stats.gather_table_stats(user,'BAR'); end;
      SQL> /
      
      PL/SQL procedure successfully completed.
      

      我明白了:

      --------------------------------------------------------------------------------------- 
      | Id  | Operation                    | Name   | Rows  | Bytes | Cost (%CPU)| Time     | 
      --------------------------------------------------------------------------------------- 
      |   0 | SELECT STATEMENT             |        |  4996 |   131K|     9  (12)| 00:00:01 | 
      |   1 |  MERGE JOIN                  |        |  4996 |   131K|     9  (12)| 00:00:01 | 
      |   2 |   TABLE ACCESS BY INDEX ROWID| BAR    |     5 |    40 |     2   (0)| 00:00:01 | 
      |   3 |    INDEX FULL SCAN           | BAR_PK |     5 |       |     1   (0)| 00:00:01 | 
      |*  4 |   SORT JOIN                  |        |  4996 | 94924 |     7  (15)| 00:00:01 | 
      |   5 |    TABLE ACCESS FULL         | FOO    |  4996 | 94924 |     6   (0)| 00:00:01 | 
      --------------------------------------------------------------------------------------- 
      

      【讨论】:

      • +1 我应该想到的。我已经看过了。由于某些原因,当表中的数据发生变化时,Oracle 的统计信息有时不会更新。这会导致非常糟糕的执行计划。手动重置统计表神奇地解决了问题。
      • 可能 95% 或更多的 Oracle 10g 安装正在运行标准统计调度程序作业,该作业在晚上 10 点启动并计算标记为 STALE 的统计信息,即那些具有优化器认为重要的段(~10%我相信)自上次计算以来的变化。因此,如果您在昨天晚上 10 点之后对表数据进行了重大更改,那么您可能会遇到此问题。标准作业还使用内部算法生成直方图,因此如果之后数据倾斜发生变化,优化器可能会被误导
      【解决方案6】:

      对 FOO 表进行全表扫描是非常合理的,该表有 4996 行,并且您对一个查询要求 oracle “发送所有 Foo 记录及其 bar.quadrant”

      【讨论】:

        猜你喜欢
        • 2020-03-19
        • 2014-03-12
        • 2011-02-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多