v$sql_plan_monitor - JOIN 估计非常不准确?

Mr.*_*ama 5 sql oracle performance oracle11g

当谈到v$sql_plan_monitorOracle 11.2 上的表时,我遇到了一些奇怪的现象。

我有两张大小合适的桌子。一个有大约 2500 万行,另一个大约有 3500 万行,两者都是约 99% 的唯一性,只有少量的重复记录。

解释计划如下(表名代替隐私,表在解释计划之前收集了统计信息):

--------------------------------------------------------------------------------------------------------------------------------
| Id  | Operation                  | Name                 | Rows  | Bytes | Cost (%CPU)| Time     |    TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT           |                      |       |       | 65611 (100)|          |        |      |            |
|   1 |  SORT AGGREGATE            |                      |     1 |    34 |            |          |        |      |            |
|   2 |   PX COORDINATOR           |                      |       |       |            |          |        |      |            |
|   3 |    PX SEND QC (RANDOM)     | :TQ10002             |     1 |    34 |            |          |  Q1,02 | P->S | QC (RAND)  |
|   4 |     SORT AGGREGATE         |                      |     1 |    34 |            |          |  Q1,02 | PCWP |            |
|*  5 |      FILTER                |                      |       |       |            |          |  Q1,02 | PCWC |            |
|*  6 |       HASH JOIN OUTER      |                      |   234K|  7770K| 65611   (1)| 00:19:41 |  Q1,02 | PCWP |            |
|   7 |        PX RECEIVE          |                      |    23M|   513M| 26409   (1)| 00:07:56 |  Q1,02 | PCWP |            |
|   8 |         PX SEND HASH       | :TQ10000             |    23M|   513M| 26409   (1)| 00:07:56 |  Q1,00 | P->P | HASH       |
|   9 |          PX BLOCK ITERATOR |                      |    23M|   513M| 26409   (1)| 00:07:56 |  Q1,00 | PCWC |            |
|* 10 |           TABLE ACCESS FULL| PRETTY_BIG_TABLE     |    23M|   513M| 26409   (1)| 00:07:56 |  Q1,00 | PCWP |            |
|  11 |        PX RECEIVE          |                      |    36M|   384M| 39164   (1)| 00:11:45 |  Q1,02 | PCWP |            |
|  12 |         PX SEND HASH       | :TQ10001             |    36M|   384M| 39164   (1)| 00:11:45 |  Q1,01 | P->P | HASH       |
|  13 |          PX BLOCK ITERATOR |                      |    36M|   384M| 39164   (1)| 00:11:45 |  Q1,01 | PCWC |            |
|* 14 |           TABLE ACCESS FULL| EVEN_BIGGER_TABLE    |    36M|   384M| 39164   (1)| 00:11:45 |  Q1,01 | PCWP |            |
--------------------------------------------------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

让我有些悲伤的Rows数字是这HASH JOIN OUTER一步的价值。
Oracle 估计它将输出大约 234k 行,这是一个相对较小的数量。我知道一个事实,在过滤*结果后,查询将返回大约 50k 行,因为它之前使用相同的数据运行以进行测试。

*:实际查询本身是一个反连接,使用 aLEFT JOIN和 aWHERE来过滤 NULL 记录。

但是,一旦查询运行,我会sql_idv$sql_plan_monitor表中检查它:

  1  SELECT
  2      plan_line_id,
  3      plan_operation,
  4      ROUND(MAX(plan_cardinality) / 1000) AS est_krows,
  5      ROUND(SUM(output_rows) / 1000) AS actual_krows
  6  FROM v$sql_plan_monitor
  7  WHERE sql_id = 'sql_id_goes_here'
  8  GROUP BY sql_id, sql_exec_id, sql_exec_start, plan_line_id, plan_operation
  9* ORDER BY sql_exec_id, plan_line_id
SQL> /

PLAN_LINE_ID PLAN_OPERATION                  EST_KROWS ACTUAL_KROWS
------------ ------------------------------ ---------- ------------
           0 SELECT STATEMENT                                     0
           1 SORT                                    0            0
           2 PX COORDINATOR                                       0
           3 PX SEND                                 0            0
           4 SORT                                    0            0
           5 FILTER                                               0
           6 HASH JOIN                             234     23084866
           7 PX RECEIVE                          23402        23168
           8 PX SEND                             23402        23168
           9 PX BLOCK                            23402        23168
          10 TABLE ACCESS                        23402        23168
          11 PX RECEIVE                          36699        17772
          12 PX SEND                             36699        17748
          13 PX BLOCK                            36699        17748
          14 TABLE ACCESS                        36699        17748
Run Code Online (Sandbox Code Playgroud)

请注意,查询仍在进行中,因此actual_krows值正在增长。

所以我的问题是:

  1. 为什么来自 HASH JOIN 的实际行数减少了五个数量级?
  2. 我该怎么做才能更准确地估计或更准确地读取实际行输出进度?它似乎只给我在 JOIN 上的可怕结果,没有别的。

Jon*_*ler 4

为什么估计是错误的?

因为从理论上讲,不可能预测一个程序是否会完成,更不用说预测它需要多长时间了。而且,实际上,估计很困难,Oracle 只有时间来满足;Oracle 不知道查询是每天提交一次还是每秒提交一千次,并且无法花费大量时间进行决定。

我们如何改进估算?

它可能有助于查看整个查询并了解有关表结构和数据分布的一些信息。这是很多信息,但不能保证会有帮助。相反,这里有一些可能对调整基数有用的方法。根据您的查询、会话、环境等,并非所有这些都有帮助。

  1. 关联统计估计声明性代码已经很困难了,当使用过程性代码时,Oracle 不会费力去尝试。如果有自定义函数,默认的估计会很差。但您可以指定自定义选择性来更改估计值。在某些极少数情况下,可能值得用具有相关统计信息的函数替换复杂的 SQL 表达式。
  2. 假统计数据 DBMS_STATS.SET_COLUMN_STATS和其他函数允许您更改估计算法的输入。但请注意,您对这一查询的修复不会破坏具有完全合理估计的其他查询。
  3. 扩展统计正如 ibre5041 所提到的,列组或表达式可能很难估计。相反,您可以让 Oracle 收集有关这些组和表达式的统计信息。然后,当它们用于查询时,估计结果可能会好得多。
  4. 重写条件 某些类型的表达式比其他表达式更难估计。如果可能的话,尝试重构你的表达方式。例如,一些复杂的NVL表达式有时用OR.
  5. SQL 配置文件“SQL 配置文件是一个数据库对象,其中包含特定于 SQL 语句的辅助统计信息。” 例如,表统计信息可能暗示只有 10% 的行连接,而配置文件可能会说“将其乘以 1000”。
  6. 未记录的提示OPT_ESTIMATE和 提示CARDINALITY可以帮助弥补错误的估计。 OPT_ESTIMATE是 SQL 配置文件使用的内容,并且是表达“嘿,将基数增加 1000%”的好方法。 CARDINALITY是表达“整个查询将返回 X 行”的简单方法。但这些提示很难使用。
  7. 动态采样像`/*+dynamic_sampling(4)*/这样的提示是告诉优化器“这是一个昂贵的查询,花时间读取现有数据,尝试一下,并调整数字”的一种方式。至少,理论上是这样。在实践中,它并不总是很有帮助。
  8. 基数反馈运行该语句两次,如果基数明显错误,Oracle 可能会在第二次修复它。
  9. 自适应查询优化12c 引入了一项功能,执行计划偶尔会检查行数,并在估计错误时自行修复。这并不能解决根本原因。而且您还无法使用它。但这听起来很酷,并且可能是开始考虑升级的一个很好的理由。

我们是否需要修正估算?

关心基数是明智的。错误的基数估计会导致许多性能问题。但在许多情况下,基数可能会出现几个数量级的错误,但并不重要。

我没有看到执行计划有任何明显的问题。两个大表以正确的方式访问(如果将使用大多数行,则全表扫描更好),连接方法很好(散列连接对于许多行来说是最好的),连接顺序很好(大表是散列的) (即第一个表),探测更大的表(即第二个表)),并且并行性良好(每个步骤都使用并行性,没有巨大行源的广播等)。

如果这个执行计划就是整个故事,我认为它是成功的。

有时,5 个数量级的偏差并不重要,尤其是当错误接近执行计划末尾时。234K 是一个足够大的数字,可以阻止很多错误,例如错误的交叉连接。

但是,如果这只是较大查询或视图的一部分,则生成的基数可能会影响其他执行计划。