【问题标题】:Why optimizer plan doesn't correlate with experimental query runs?为什么优化器计划与实验查询运行不相关?
【发布时间】:2016-11-06 23:44:20
【问题描述】:

假设我们有以下问题:

  • 给定一个包含一列'X' 的表,其中包含一些随机的行 从 1 到 100 的整数:

    CREATE TABLE xtable(x) AS 
       SELECT ceil(dbms_random.value * 100) 
         FROM dual
       CONNECT BY level <= 1000000;
    
  • 我们必须删除重复的行,以便所有不同的整数都保留在表中。


让我们考虑下面的三个解决方案(平均执行时间和优化器计划)。

我必须补充一下实验表明:

  • 解决方案 1 和 2 具有可扩展性,并且随着每个行数量步骤的增加而线性时间增长(使用多达 1000 万行的表进行测试)
  • 解决方案 3 具有指数时间增长,大致类似于 3 * exp(0.6 * N)

我们看到对于solution 2优化器计划给出与实验结果无关的期望, 甚至与他们相反:

  • 计划 2 和 3 中的成本和其他值几乎相同
  • 解决方案 1 和 2 的执行时间几乎相同

在这个实验中,表的收集统计信息的存在与否 不会影响优化器计划和执行时间。


请解释为什么我不能信任案例 2 中的优化器计划。

是什么导致优化器忽略了线性复杂度和指数复杂度之间的明显区别?


解决方案:
1.

DELETE xtable WHERE rowid IN (
      SELECT ri from (
         SELECT rowid                                             AS ri,
                row_number() OVER(PARTITION BY x ORDER BY null) AS rn
           FROM xtable
      )
      WHERE rn > 1
)


Exe time: 14 - 16 secs

Plan:
------------------------------------------------------------------------------------
| Id  | Operation                | Name     | Rows    | Bytes    | Cost | Time     |
------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT         |          | 1000000 | 15000000 | 5119 | 00:00:01 |
|   1 |   DELETE                 | XTABLE   |         |          |      |          |
| * 2 |    HASH JOIN SEMI        |          | 1000000 | 15000000 | 5119 | 00:00:01 |
|   3 |     TABLE ACCESS FULL    | XTABLE   | 1000000 |  3000000 |  280 | 00:00:01 |
|   4 |     VIEW                 | VW_NSO_1 | 1000000 | 12000000 | 2976 | 00:00:01 |
| * 5 |      VIEW                |          | 1000000 | 25000000 | 2976 | 00:00:01 |
|   6 |       WINDOW SORT        |          | 1000000 |  3000000 | 2976 | 00:00:01 |
|   7 |        TABLE ACCESS FULL | XTABLE   | 1000000 |  3000000 |  280 | 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 2 - access(ROWID="RI")
* 5 - filter("RN">1)

2.

DELETE xtable WHERE (x, rowid) NOT IN (SELECT x, min(rowid) FROM xtable GROUP BY x)

Exe time: 15 - 17 secs

Plan:
--------------------------------------------------------------------------------------
| Id | Operation                 | Name   | Rows    | Bytes   | Cost      | Time     |
--------------------------------------------------------------------------------------
|  0 | DELETE STATEMENT          |        |   50000 |  150000 | 278162850 | 03:01:06 |
|  1 |   DELETE                  | XTABLE |         |         |           |          |
|  2 |    FILTER                 |        |         |         |           |          |
|  3 |     TABLE ACCESS FULL     | XTABLE | 1000000 | 3000000 |       281 | 00:00:01 |
|  4 |     FILTER                |        |         |         |           |          |
|  5 |      SORT GROUP BY NOSORT |        | 1000000 | 3000000 |       280 | 00:00:01 |
|  6 |       TABLE ACCESS FULL   | XTABLE | 1000000 | 3000000 |       280 | 00:00:01 |
--------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
------------------------------------------
* 5 - access(INTERNAL_FUNCTION("X")=INTERNAL_FUNCTION("X") AND INTERNAL_FUNCTION(ROWID)=INTERNAL_FUNCTION("MIN(ROWID)"))
* 5 - filter(INTERNAL_FUNCTION(ROWID)=INTERNAL_FUNCTION("MIN(ROWID)") AND INTERNAL_FUNCTION("X")=INTERNAL_FUNCTION("X"))

3.

DELETE xtable a WHERE EXISTS(select 1 FROM xtable b WHERE a.x = b.x AND a.rowid < b.rowid)

Exe time: 970 - 990 sec

Plan:
----------------------------------------------------------------------------------------------
| Id  | Operation                        | Name   | Rows    | Bytes   | Cost      | Time     |
----------------------------------------------------------------------------------------------
|   0 | DELETE STATEMENT                 |        |   50000 |  300000 | 278208956 | 03:01:08 |
|   1 |   DELETE                         | XTABLE |         |         |           |          |
| * 2 |    FILTER                        |        |         |         |           |          |
|   3 |     NESTED LOOPS SEMI            |        |   50000 |  300000 | 278208956 | 03:01:08 |
|   4 |      TABLE ACCESS FULL           | XTABLE | 1000000 | 3000000 |       280 | 00:00:01 |
| * 5 |      TABLE ACCESS BY ROWID RANGE | XTABLE |   50000 |  150000 |       278 | 00:00:01 |
----------------------------------------------------------------------------------------------    
Predicate Information (identified by operation id):
------------------------------------------
* 2 - filter(:VAR2=:VAR1)
* 5 - access("B".ROWID>"A".ROWID)

计划是在Oracle 12.1.0.2.0获得的

【问题讨论】:

  • 当您说“在情况 2 中不能信任优化器计划”时,您是什么意思?是什么让您认为 2 和 3 的执行计划相似?执行计划 3 有两个全表扫描,由嵌套循环半连接,然后过滤。执行计划 2 有一个排序,后跟一个过滤器,然后使用它来过滤全表扫描的结果。这更类似于第一个执行计划,恕我直言。
  • @Boneist,我考虑了 Rows、Bytes、Cost、Time 列中 Total 值的相似性。令人惊讶的是,它们几乎相同,并且当我们用不同数量的行填充表时同步变化:1000、10 000、...、10 000 000
  • 加一个很好的准备Q。请提供Oracle版本并在解释计划中添加谓词信息。获取信息见here

标签: oracle database-performance oracle12c sqlperformance cost-based-optimizer


【解决方案1】:

请解释为什么我不能相信案例 2 中的优化器计划。

你永远不应该相信优化器。 CBO 是 95% 正确的,但你不知道哪 5% 是错误的。

典型的问题是使用EXPLAIN PLAN 显示的执行计划不等于执行使用的计划。 (你没有说你是如何获得计划的)。

如有疑问,请使用DBMS_SQLTUNE.REPORT_SQL_MONITOR 进行长期查询以查看实际计划和有问题的部分。

是什么导致优化器忽略了线性复杂度和指数复杂度之间的明显区别?

见上文,忘记计划的成本比较。在处理 整个表 时要避免的是 NESTED LOOP 处理。 这正是案例 3 中发生的情况。

 |  3 |     NESTED LOOPS SEMI            |       |   50000|  300000 | 278208956 | 03:01:08|
 |  4 |      TABLE ACCESS FULL           |XTABLE | 1000000| 3000000 |       280 | 00:00:01|
 |  5 |      TABLE ACCESS BY ROWID RANGE |XTABLE |   50000|  150000 |       278 | 00:00:01|

您想查看 SORT 和 HASH JOIN 这是计划 1 显示的内容。

在我看来,计划 2 不会随着重复记录的数量而扩展(简单地尝试使用每行两次的表格,看看您是否获得与案例 3 相同的经过时间) . 优化器无法估计重复记录的数量,因此防御性地估计高数量,因此成本高。

最后一句话 - 理论说你不应该观察线性行为,但最好是O(n * log(n))

最后一句话 - 您的测试数据对于删除重复数据是不现实的。通常,您有一张带有少量重复数据的大桌子。在您的设置中,除 100 之外的所有记录都是重复记录。

删除成本高于查找副本的成本,因此您可以观察到线性行为。

试试

CREATE TABLE xtable(x) AS 
   SELECT ceil(dbms_random.value * 100000000) 
     FROM dual
   CONNECT BY level <= 1000000;

select count(*) total, count(*)- count(distinct x) to_be_deleted from xtable;
     TOTAL TO_BE_DELETED
---------- -------------
   1000000          5083  

因此您将删除 0.5% 的记录。现在缩放,您将观察到完全不同的模式。

【讨论】:

  • 感谢您提供有用的信息。我同意 O(n * log(n)),但令人惊讶的结果是 100,000 行:1.65 秒; 1,000,000 行 - 15.8; 10,000,000 - 138.6 秒;甚至比预期的低一点。我认为低质量的测试数据抵消了常见的理论效应。
  • @diziaq 补充说明
【解决方案2】:

无法重现第二个计划。来了:

-------------------------------------------------------------------------------------------                                       
| Id  | Operation              | Name     | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |                                       
-------------------------------------------------------------------------------------------                                       
|   0 | DELETE STATEMENT       |          |       |       |       |  3648 (100)|          |                                       
|   1 |  DELETE                | XTABLE   |       |       |       |            |          |                                       
|   2 |   MERGE JOIN ANTI NA   |          |   999K|    26M|       |  3648   (5)| 00:00:01 |                                       
|   3 |    SORT JOIN           |          |  1000K|  2929K|    22M|  3147   (3)| 00:00:01 |                                       
|   4 |     TABLE ACCESS FULL  | XTABLE   |  1000K|  2929K|       |   434   (3)| 00:00:01 |                                       
|*  5 |    SORT UNIQUE         |          |   100 |  2500 |       |   500  (16)| 00:00:01 |                                       
|   6 |     VIEW               | VW_NSO_1 |   100 |  2500 |       |   499  (16)| 00:00:01 |                                       
|   7 |      SORT GROUP BY     |          |   100 |   300 |       |   499  (16)| 00:00:01 |                                       
|   8 |       TABLE ACCESS FULL| XTABLE   |  1000K|  2929K|       |   434   (3)| 00:00:01 |                                       
-------------------------------------------------------------------------------------------        

【讨论】:

  • 看起来和我预期的一样。似乎是我的数据库有异常或棘手的设置或其他东西。现在真的不重要了。困扰我的是如何确定数据库执行查询的方式。我很困惑,因为以前从未遇到过这样的问题,并认为 EXPLAIN PLAN 至少大致显示了查询运行时的预期结果。
  • @diziaq 你有什么 Oracle 版本?我在 11 和 12 中测试过,执行计划是一样的(当我们不计算成本时)。你永远无法确定。您可以使用 SQL 计划基线
  • 我昨天和今天使用不同的实例多次获得了 Oracle 12.1 的计划。问完这个问题后,我在 Oracle 11.2 上尝试了相同的查询,并且和你一样,我无法重现我询问的计划。所以这看起来像是一场意外,但我想知道是什么原因造成的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-20
  • 2023-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-08
  • 1970-01-01
相关资源
最近更新 更多