暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

opt_estimate3

原创 张程 2020-08-06
922

原文作者:Jonathan lewis
原文地址:https://jonathanlewis.wordpress.com/2019/06/28/opt_estimate-3/
译文如下:

对于opt_estimate()提示,这只是一个简短的提示:它们可能是最有用的变体,因为它们解决了优化器可能产生持续错误的CARDINALITY估算的问题。第一个是“group by”选项——这个提示我曾经称之为“战略性”提示,但更确切地说,应该称之为“查询块”提示。以下是最简单的示例(在12.2、18.3和19.2下测试):

rem
rem     Script:         opt_est_gby.sql
rem     Author:         Jonathan Lewis
rem     Dated:          June 2019
rem 
 
create table t1
as
select
        rownum                  id,
        mod(rownum,200)         n1,
        lpad(rownum,10,'0')     v1,
        rpad('x',100)           padding
)
from
        dual
connect by
        level <= 3000
;
 
set autotrace on explain
 
prompt  =============================
prompt  Baseline cardinality estimate
prompt  (correct cardinality is 10)
prompt  Estimate will be 200
prompt  =============================
 
select  /*+
                qb_name(main)
        */
        mod(n1,10), count(*) 
from    t2 
group by 
        mod(n1,10)
;

我已经生成了一个包含3000行的表,其中列n1包含15类值,每类包含200个不同的值。然后,查询在mod(n1,10)上聚合操作,因此它只会返回10行,但是优化器没有一个机制来推断这一点,在第一步的HASH GROUP BY操作上的Rows值是我们真正感兴趣的。

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |   200 |   800 |    10  (10)| 00:00:01 |
|   1 |  HASH GROUP BY     |      |   200 |   800 |    10  (10)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
---------------------------------------------------------------------------

似乎优化器的默认位置是使用底层列的num_distinct作为聚合的估计值。我们可以用opt_estimate()提示,用通常的两种方法来解决这个问题。首先,让我们告诉优化器它高估了基数10倍:

select  /*+
                qb_name(main)
                opt_estimate(@main group_by, scale_rows = 0.1)
        */
        mod(n1,10), count(*) 
from    t1 
group by 
        mod(n1,10)
;
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |    20 |    80 |    10  (10)| 00:00:01 |
|   1 |  HASH GROUP BY     |      |    20 |    80 |    10  (10)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
---------------------------------------------------------------------------

HINT使用group_by作为关键选项参数,然后使用标准的scale_rows = nnn设置比例因子,该比例因子应用于调整默认计算的结果。设为10%(0.1)时,我们估算有20行。
或者,我们可以简单地告诉优化器,我们希望它相信将为聚合生成多少行——让我们告诉它,结果将是10行。

select  /*+
                qb_name(main)
                opt_estimate(@main group_by, rows = 10)
        */
        mod(n1,10), count(*) 
from    t1 
group by 
        mod(n1,10)
;
 
---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |    10 |    40 |    10  (10)| 00:00:01 |
|   1 |  HASH GROUP BY     |      |    10 |    40 |    10  (10)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
---------------------------------------------------------------------------

我们将相同的group_by用作关键参数,并且通过rows = nnn。

下一步:

在聚合之后,通常会有一个“having”子句,因此如果您知道having子句的正常效果,那么可以考虑使用group_by选项来修复having子句的基数。例如:“having count()>NNN”将使用优化器的标准5%“可选择率”和“having count()=NNN”将使用标准的1%“可选择率”。但是,在看过group_by options之后,我猜测opt_estimate()提示可能也有一个having选项,所以我尝试了一下–启用了autotrace之后,这里有三个查询,首先是没有HINT的基线(在having子句中使用标准的5%“可选择率”),然后是几个其他查询,其中有一些提示可以调整基数:

select  /*+
                qb_name(main)
        */
        mod(n1,10), count(*)
from    t1
group by
        mod(n1,10)
having
        count(*) > 100
;
 
select  /*+
                qb_name(main)
                opt_estimate(@main having scale_rows=0.4)
        */
        mod(n1,10), count(*)
from    t1
group by
        mod(n1,10)
having
        count(*) > 100
;
 
select  /*+
                qb_name(main)
                opt_estimate(@main group_by scale_rows=2)
                opt_estimate(@main having scale_rows=0.3)
        */
        mod(n1,10), count(*)
from    t1
group by
        mod(n1,10)
having
        count(*) > 100
;

第一个查询给我们的基线基数是10(200的5%)。第二个查询将having基数缩小0.4倍(表示估计值为4)。最后一个查询将group by cardinality加倍(到400),然后将having cardinality(本应为20)缩小0.3倍,产生1个cardinality为 6的效果。下面是执行计划:

----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |    10 |    40 |    10  (10)| 00:00:01 |
|*  1 |  FILTER             |      |       |       |            |          |   --  10
|   2 |   HASH GROUP BY     |      |    10 |    40 |    10  (10)| 00:00:01 |   -- 200
|   3 |    TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
----------------------------------------------------------------------------
 
----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     4 |    16 |    10  (10)| 00:00:01 |
|*  1 |  FILTER             |      |       |       |            |          |    --   4
|   2 |   HASH GROUP BY     |      |     4 |    16 |    10  (10)| 00:00:01 |    -- 200
|   3 |    TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
----------------------------------------------------------------------------
 
----------------------------------------------------------------------------
| Id  | Operation           | Name | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |      |     6 |    24 |    10  (10)| 00:00:01 |
|*  1 |  FILTER             |      |       |       |            |          |    --   6
|   2 |   HASH GROUP BY     |      |     6 |    24 |    10  (10)| 00:00:01 |    -- 400
|   3 |    TABLE ACCESS FULL| T1   |  3000 | 12000 |     9   (0)| 00:00:01 |
----------------------------------------------------------------------------

有点遗憾的是,FILTER操作不显示估算值,而HASH GROUP BY操作显示的是应用了having子句之后的估计值。很高兴看到执行计划报告了我在步骤1和2的末尾添加的提示。

你可能会想知道,为什么会想增加对group by的估计,然后减少对having的估算。虽然我不打算费心去创建一个有效的示例,但是应该不难理解这样一个想法:优化器可能会使用复杂视图合并将group by推迟到联接之后,所以增加group by的估计值可能是必要的,以确保接下来的特定转换不会发生,通过对having的缩减,可以确保下一个联接是嵌套循环而不是HASH联接。当然,如果您不需要如此精细,您可以简单地利用opt_estimate()提示的另一个选项query_block选项–但这(可能)将出现在本系列的下一篇文章中。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论