为什么 CBO 不选择没有统计数据的低成本跳过扫描?

Jac*_*las 5 oracle optimization oracle-11g-r2

在下面的脚本中,CBO 选择INDEX FAST FULL SCAN了一个INDEX SKIP SCAN(这通过使用 UNION ALL 的查询来说明,但您可以证明它是正确的,只需explain plan for select * from foo where baz=1)。尽管跳过扫描是成本较低的选择,但还是做出了这一选择。

一旦表被分析,跳过扫描是首选(尽管相对成本实际上上升)。CBO 似乎根本不考虑使用动态采样进行跳过扫描,这是真的吗?如果没有,为什么在收集统计数据之前没有选择跳过扫描?

架构:

create table foo( bar integer, 
                  baz integer, 
                  qux char(99), 
                  constraint pk_foo primary key (bar, baz) )
             organization index compress;
--table FOO created.
insert into foo(bar,baz) select mod(level,1000), level from dual connect by level<1000000;
--999,999 rows inserted.
commit;
--committed.
Run Code Online (Sandbox Code Playgroud)

预分析:

explain plan for
select * from foo where baz=1
union all
select /*+ full(foo) */ * from foo where baz=1
union all
select /*+ index_ss(foo) */ * from foo where baz=1;
--plan FOR succeeded.

select * from table(dbms_xplan.display);
/*
--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |   132 | 16764 |  8480  (53)| 00:01:34 |
|   1 |  UNION-ALL            |        |       |       |            |          |
|*  2 |   INDEX FAST FULL SCAN| PK_FOO |    44 |  5588 |  4034   (1)| 00:00:45 | <-- why full?
|*  3 |   INDEX FAST FULL SCAN| PK_FOO |    44 |  5588 |  4034   (1)| 00:00:45 |
|*  4 |   INDEX SKIP SCAN     | PK_FOO |    44 |  5588 |   411   (0)| 00:00:05 |
--------------------------------------------------------------------------------

Note
-----
   - dynamic sampling used for this statement (level=2)
*/
Run Code Online (Sandbox Code Playgroud)

分析:

analyze table foo compute statistics;
--table FOO analyzed.
Run Code Online (Sandbox Code Playgroud)

事后分析:

explain plan for
select * from foo where baz=1
union all
select /*+ full(foo) */ * from foo where baz=1
union all
select /*+ index_ss(foo) */ * from foo where baz=1;
--plan FOR succeeded.

select * from table(dbms_xplan.display);
/*
--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |     3 |    30 |  6038  (84)| 00:01:07 |
|   1 |  UNION-ALL            |        |       |       |            |          |
|*  2 |   INDEX SKIP SCAN     | PK_FOO |     1 |    10 |  1003   (1)| 00:00:12 | <-- makes sense
|*  3 |   INDEX FAST FULL SCAN| PK_FOO |     1 |    10 |  4033   (1)| 00:00:45 |
|*  4 |   INDEX SKIP SCAN     | PK_FOO |     1 |    10 |  1003   (1)| 00:00:12 |
--------------------------------------------------------------------------------
*/
Run Code Online (Sandbox Code Playgroud)

这里这里的SQLFiddle 上有类似的东西

Bal*_*app 7

CBO 似乎根本不考虑使用动态采样进行跳过扫描,这是真的吗?

其实这很容易验证,你可以通过启用 10053 跟踪来做到这一点。您会看到优化器甚至根本不考虑跳过扫描。原因是“_optimizer_skip_scan_guess”参数。此参数的默认值是 FALSE,这意味着优化器在它所拥有的只是“猜测”选择性时不会考虑跳过扫描,动态采样就是这种情况。

如果您将“_optimizer_skip_scan_guess”设置为 TRUE,则将考虑跳过扫描,这也可以再次通过 10053 跟踪确认。

PS:您的 db_file_multiblock_read_count 参数似乎低于默认值。在我的11.2.0.4沙箱上,默认值为128,在对表进行统计后,索引FFS的开销大约是索引SS的三分之一。

编辑:添加输出

SQL> create table foo( bar integer, baz integer, qux char(99), constraint pk_foo primary key (bar, baz) ) organization index compress;

Table created.

SQL> insert into foo(bar,baz) select mod(level,1000), level from dual connect by level<1000000;

999999 rows created.

SQL> commit;

Commit complete.
Run Code Online (Sandbox Code Playgroud)

原来的:

SQL> explain plan for
select * from foo where baz=1
union all
select /*+ full(foo) */ * from foo where baz=1
union all
select /*+ index_ss(foo) */ * from foo where baz=1;  2    3    4    5    6

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1715140356

--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |    66 |  8382 |  1302   (1)| 00:00:16 |
|   1 |  UNION-ALL            |        |       |       |            |          |
|*  2 |   INDEX FAST FULL SCAN| PK_FOO |    22 |  2794 |   546   (1)| 00:00:07 |
|*  3 |   INDEX FAST FULL SCAN| PK_FOO |    22 |  2794 |   546   (1)| 00:00:07 |
|*  4 |   INDEX SKIP SCAN     | PK_FOO |    22 |  2794 |   211   (0)| 00:00:03 |
--------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("BAZ"=1)
   3 - filter("BAZ"=1)
   4 - access("BAZ"=1)
       filter("BAZ"=1)

Note
-----

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
   - dynamic sampling used for this statement (level=2)
Run Code Online (Sandbox Code Playgroud)

为猜测的选择性启用跳过扫描:

SQL> alter session set "_optimizer_skip_scan_guess"=true;

Session altered.

SQL> explain plan for
select * from foo where baz=1
union all
select /*+ full(foo) */ * from foo where baz=1
union all
select /*+ index_ss(foo) */ * from foo where baz=1;  2    3    4    5    6

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3033162421

--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |    66 |  8382 |   772   (1)| 00:00:10 |
|   1 |  UNION-ALL            |        |       |       |            |          |
|*  2 |   INDEX SKIP SCAN     | PK_FOO |    22 |  2794 |   211   (0)| 00:00:03 |
|*  3 |   INDEX FAST FULL SCAN| PK_FOO |    22 |  2794 |   350   (1)| 00:00:05 |
|*  4 |   INDEX SKIP SCAN     | PK_FOO |    22 |  2794 |   211   (0)| 00:00:03 |
--------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("BAZ"=1)
       filter("BAZ"=1)
   3 - filter("BAZ"=1)
   4 - access("BAZ"=1)
       filter("BAZ"=1)

Note

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
-----
   - dynamic sampling used for this statement (level=2)

24 rows selected.
Run Code Online (Sandbox Code Playgroud)

只是为了完整性:

SQL> analyze table foo compute statistics;

Table analyzed.

SQL> explain plan for
select * from foo where baz=1
union all
select /*+ full(foo) */ * from foo where baz=1
union all
select /*+ index_ss(foo) */ * from foo where baz=1;  2    3    4    5    6

Explained.

SQL>  select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1715140356

--------------------------------------------------------------------------------
| Id  | Operation             | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |        |     3 |    30 |  1717   (2)| 00:00:21 |
|   1 |  UNION-ALL            |        |       |       |            |          |
|*  2 |   INDEX FAST FULL SCAN| PK_FOO |     1 |    10 |   357   (3)| 00:00:05 |
|*  3 |   INDEX FAST FULL SCAN| PK_FOO |     1 |    10 |   357   (3)| 00:00:05 |
|*  4 |   INDEX SKIP SCAN     | PK_FOO |     1 |    10 |  1002   (0)| 00:00:13 |
--------------------------------------------------------------------------------

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - filter("BAZ"=1)
   3 - filter("BAZ"=1)
   4 - access("BAZ"=1)
       filter("BAZ"=1)

19 rows selected.
Run Code Online (Sandbox Code Playgroud)