Simple example
First, we verify that the init parameter
init parameters
is set to the (default) value of 2:
select
value
from
v$parameter
where
name = 'optimizer_dynamic_sampling';
create table tq84_dyn_smp_tst (
id integer, -- primary key,
val number(7,2)
);
insert into tq84_dyn_smp_tst
select
level,
dbms_random.value(1000, 100000)
from
dual connect by level <= 10000;
commit;
Because there are no table statistics for the table and the optimizer_dynamic_sampling
is set to two, Oracle will use dynamic statistics in the following SQL statement:
explain plan for
select
*
from
tq84_dyn_smp_tst
where
id = 1;
select * from dbms_xplan.display(format=>'basic rows note');
--
-- ------------------------------------------------------
-- | Id | Operation | Name | Rows |
-- ------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 1 |
-- | 1 | TABLE ACCESS FULL| TQ84_DYN_SMP_TST | 1 |
-- ------------------------------------------------------
--
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
We now gather table statistics …
begin
dbms_stats.gather_table_stats(user, 'tq84_dyn_smp_tst');
end;
/
… and inspect the new execution plan. This time, because there are statistics, Oracle will not dynamically sample the table again:
explain plan for
select
*
from
tq84_dyn_smp_tst
where
id = 1;
select * from dbms_xplan.display(format=>'basic rows note');
--
-- ------------------------------------------------------
-- | Id | Operation | Name | Rows |
-- ------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 1 |
-- | 1 | TABLE ACCESS FULL| TQ84_DYN_SMP_TST | 1 |
-- ------------------------------------------------------
We change the value of id
to 1 in each record (thus the where id = 1
condition will essentially become meaningless):
update tq84_dyn_smp_tst set id=1;
Again, the execution plan. Note that the plan, notably, the number of estimated rows, did not change: Oracle still thinks the query will return 1 row (which is of course wrong).
explain plan for
select
*
from
tq84_dyn_smp_tst
where
id = 1;
select * from dbms_xplan.display(format=>'basic rows note');
--
-- ------------------------------------------------------
-- | Id | Operation | Name | Rows |
-- ------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 1 |
-- | 1 | TABLE ACCESS FULL| TQ84_DYN_SMP_TST | 1 |
-- ------------------------------------------------------
In order to force Oracle to use dynamic samples, we use the
dynamic_sampling
hint.
Because dynamic sampling will be used, the number of returned rows is now correctly estimated to 10000:
explain plan for
select /*+ dynamic_sampling(tq84_dyn_smp_tst 1) */
*
from
tq84_dyn_smp_tst
where
id = 1;
select * from dbms_xplan.display(format=>'basic rows note');
--
-- ------------------------------------------------------
-- | Id | Operation | Name | Rows |
-- ------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 10000 |
-- | 1 | TABLE ACCESS FULL| TQ84_DYN_SMP_TST | 10000 |
-- ------------------------------------------------------
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
Cleaning up
drop table tq84_dyn_smp_tst;
/* OPT_DYN_SAMP */ and /* DS_SVC */ comments
When an SQL statement is executed with dynamic optimizer statistics, the statement is stored in the shared pool with one of the two comments (not
hints)
/* OPT_DYN_SAMP */
or
/* DS_SVC */
.
/* OPT_DYN_SAMP */
comments indicate that regular statistics were not sufficient to estimate a cardinality, or such statistics were even missing.
/* DS_SVC */
comments were used in 11gR2 and 12c.
In Oracle 19c, there seems to also be comments like /* SQL Analyze(181,0) */
.
TODO
The following tests were conducted with the default value of optimizer_dynamic_sampling
which is 2:
select * from v$parameter where name in ('optimizer_dynamic_sampling');
A table is needed for the test:
create table tq84_t(id number, val varchar2(10));
The table needs some data::
insert into tq84_t
select
mod(level, 3) *
mod(level, 5) *
mod(level, 7) *
mod(level, 11) +
mod(level, 10101),
'xxx'
from
dual connect by level <= 100000;
The insert statement didn't generate any sort of auto statistics, num_rows
and blocks
is reported as null
:
select
num_rows,
blocks
from
user_tables
where
table_name = 'TQ84_T';
There are 11 rows in the table whose id
is 111
:
select count(*) from tq84_t where id = 111;
Because of the missing statistics, the optimizer uses dynamic statistics (referred to as «dynamic sampling» in the note of the following output) when a simple select
statement is explained. With dynamic sampling, the optimizer estimates the number of records with id = 111
to be 4
:
explain plan for
select * from tq84_t where id = 111;
select * from table(dbms_xplan.display);
--
-- ----------------------------------------------------------------------------
-- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-- ----------------------------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 4 | 80 | 68 (0)| 00:00:01 |
-- |* 1 | TABLE ACCESS FULL| TQ84_T | 4 | 80 | 68 (0)| 00:00:01 |
-- ----------------------------------------------------------------------------
--
- …
--
-- Note
-- -----
-- - dynamic statistics used: dynamic sampling (level=2)
Gathering statistics for the table;
begin
dbms_stats.gather_table_stats(user, 'tq84_t');
end;
/
This time, explaining the plan for the same select statement shows that with gathered statistics, the estimation of returned rows is 10
, much better than before, but not correct. Also: the note of the previous plan about dynamic statistics used is gone:
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10 | 80 | 69 (2)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TQ84_T | 10 | 80 | 69 (2)| 00:00:01 |
----------------------------------------------------------------------------
set serveroutput off
select /*+ gather_plan_statistics */
*
from tq84_t where id = 111;
select * from table(dbms_xplan.display_cursor(format => 'allstats last'));
--
-- --------------------------------------------------------------------------------------
-- | Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers |
-- --------------------------------------------------------------------------------------
-- | 0 | SELECT STATEMENT | | 1 | | 11 |00:00:00.01 | 202 |
-- |* 1 | TABLE ACCESS FULL| TQ84_T | 1 | 10 | 11 |00:00:00.01 | 202 |
-- --------------------------------------------------------------------------------------
E-Rows
shows the estimated rows (= 10, as estimated in the execution plan), while A-Rows
(= actual rows) shows the value of the actual rows.
Note, I used
set serveroutput off
in order to prevent
SQL Developer from executing the statement which reads the
dbms_output
buffer which would have interfered with the last
dbms_output.display_cursor
statement.