Thursday, September 17, 2020


 


Hi DBA Friends,

Oracle 19c New Feature - Real-Time Statistices explored with an Demo:


Oracle Database 19c introduces real-time statistics, which extends online statistics gathering to also include conventional DML statements.


Statistics are normally gathered by automatic statistics gather job which runs inside the database maintenance window – but that is just once a day.


But for volatile tables statistics can go stale between DBMS_STATS job executions, so the new Oracle 19c feature of real-time statistics can help the optimizer generate more optimal plans for such volatile tables.


Bulk load operations will gather all necessary statistics (pre Oracle 19c behavior)- however real-time statistics augment rather than replace traditional statistics.


We have a table called MYOBJECTS which currently has 72383 rows.


SQL> show pdbs


    CON_ID CON_NAME                       OPEN MODE  RESTRICTED

---------- ------------------------------ ---------- ----------

         2 PDB$SEED                       READ ONLY  NO

         3 SRLABPLUG                      READ WRITE NO

SQL>

SQL> create table myobjects as select * from dba_objects;


Table created.


SQL> set lines 300 pages 20000

SQL>

SQL> select distinct object_type from myobjects where owner ='SYS';


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


We now insert some additional rows into the table – basically doubling the number of rows in the table.


In earlier versions there is now a possibility of sub-optimal plans being chosen by the optimizer as it is ‘not aware’ of the fact that some DML activity has happened on the table and now the number of rows in the table have increased two-fold.


But now in Oracle 19c, we can see that as part of the INSERT statement, an OPTIMIZER STATISTICS GATHERING operation was also performed.


insert into myobjects select * from myobjects;


commit;


SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format=>'TYPICAL'));


When a hard parse of the SQL statement occurs, we can see that that the optimizer has detected that additional rows have been added to the table,


This is also indicated in the Note section: dynamic statistics used: statistics for conventional DML


EXEC DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO;


alter system flush shared_pool;


select distinct object_type from myobjects where owner='SYS';


select * from table (dbms_xplan.display_cursor);


Real-time statistics are indicated by STATS_ON_CONVENTIONAL_DML in the NOTES column in the *_TAB_STATISTICS and *_TAB_COL_STATISTICS views.


SELECT NUM_ROWS, BLOCKS, NOTES FROM  DBA_TAB_STATISTICS WHERE  TABLE_NAME = 'MYOBJECTS';



Prerequisites:

--------------


This feature is currently restricted to Enterprise Edition on Engineered Systems, like Exadata and Exadata Cloud Service, as described here. There is a workaround for testing by enabling the "_exadata_feature_on" initialisation parameter. Don't use this on a real instance or you will be breaking your license agreement.



alter system set "_exadata_feature_on"=true scope=spfile;

shutdown immediate;

startup;


--DROP USER testuser1 CASCADE;

CREATE USER testuser1 IDENTIFIED BY testuser1

  QUOTA UNLIMITED ON users;

GRANT CREATE SESSION, CREATE TABLE TO testuser1;


We also need a few extra grants, just for our testing.


-- For resetting the test data.

GRANT CREATE PROCEDURE TO testuser1;


-- So we can use the DBMS_XPLAN.DISPLAY_CURSOR table function.

GRANT SELECT_CATALOG_ROLE TO testuser1;


-- So we can use the DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO procedure.

GRANT ANALYZE ANY TO testuser1;


Connect to test user and create a test table.


CONN c##testuser1/testuser1@//192.168.58.101:1521/SRLABPLUG


CREATE TABLE tab1 (

  id           NUMBER,

  description  VARCHAR2(50)

);


Create a procedure to truncate the test table, populate it with some data and gather all table statistics.


CREATE OR REPLACE PROCEDURE reset_tab1 AS

BEGIN

  EXECUTE IMMEDIATE 'TRUNCATE TABLE tab1';

  INSERT /*+ APPEND */ INTO tab1 (id, description)

    SELECT level, 'Description of ' || level

    FROM   dual

    CONNECT BY level <= 1000;

  COMMIT;

  DBMS_STATS.gather_table_stats(NULL, 'tab1');

END;

/


Format of Each Test

Before we run a test we will reset the test table using the following procedure call.


EXEC reset_tab1;



It can take some time for monitoring information to be written to the *_TAB_MODIFICATIONS, *_TAB_STATISTICS and *_IND_STATISTICS views, so after each test we will use the DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO procedure to immediately flush the information to the dictionary.


EXEC DBMS_STATS.flush_database_monitoring_info;


We will then run the following queries to show some of the statistics information for the table and columns. These are not the only statistics that are affected, but they are useful to give us an indication of what is happening. The output below represents the starting point after the reset.


-- SQL*Plus and SQLcl formatting.

SET LINESIZE 100 PAGESIZE 1000

COLUMN table_name FORMAT A10

COLUMN partition_name FORMAT A15

COLUMN column_name FORMAT A11

COLUMN notes FORMAT A25

COLUMN high_value FORMAT 9999999999

COLUMN low_value FORMAT 9999999999



-- NUM_ROWS from USER_TABLES.

SELECT table_name,

       num_rows

FROM   user_tables

WHERE  table_name = 'TAB1';


TABLE_NAME   NUM_ROWS

---------- ----------

TAB1             1000


SQL>



-- NUM_ROWS from USER_TAB_STATISTICS.

SELECT table_name,

       num_rows,

       notes

FROM   user_tab_statistics

WHERE  table_name = 'TAB1';


TABLE_NAME   NUM_ROWS NOTES

---------- ---------- -------------------------

TAB1             1000


SQL>



-- LOW_VALUE and HIGH_VALUE of ID column from USER_TAB_COL_STATISTICS.

SELECT table_name,

       column_name,

       UTL_RAW.cast_to_number(low_value) AS low_value,

       UTL_RAW.cast_to_number(high_value) AS high_value,

       sample_size,

       notes

FROM   user_tab_col_statistics

WHERE  table_name = 'TAB1'

AND    column_name = 'ID';


TABLE_NAME COLUMN_NAME   LOW_VALUE  HIGH_VALUE SAMPLE_SIZE NOTES

---------- ----------- ----------- ----------- ----------- -------------------------

TAB1       ID                    1        1000        1000


SQL>


Conventional Path INSERT ... SELECT:


We reset the test data and run a conventional path INSERT ... SELECT to create 1000 rows.


-- Reset the test data.

EXEC reset_tab1;


-- Conventional path insert. (rows 1001-2000)

INSERT INTO tab1

SELECT level+1000 AS id,

       'Description of ' || level AS description

FROM   dual

CONNECT BY level <= 1000;

COMMIT;


We check the execution plan of the statement using the DBMS_XPLAN.DISPLAY_CURSOR table function and we can see an "OPTIMIZER STATISTICS GATHERING" operation in the plan.


SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);


We flush the monitoring information to the dictionary.


EXEC DBMS_STATS.flush_database_monitoring_info;


We check some basic statistics for the TAB1 table.


-- NUM_ROWS from USER_TABLES.

SELECT table_name,

       num_rows

FROM   user_tables

WHERE  table_name = 'TAB1';


-- NUM_ROWS from USER_TAB_STATISTICS.

SELECT table_name,

       num_rows,

       notes

FROM   user_tab_statistics

WHERE  table_name = 'TAB1';


-- LOW_VALUE and HIGH_VALUE of ID column from USER_TAB_COL_STATISTICS.

SELECT table_name,

       column_name,

       UTL_RAW.cast_to_number(low_value) AS low_value,

       UTL_RAW.cast_to_number(high_value) AS high_value,

       sample_size,

       notes

FROM   user_tab_col_statistics

WHERE  table_name = 'TAB1'

AND    column_name = 'ID';


Activity Logs:

--------------



SQL> show pdbs


    CON_ID CON_NAME                       OPEN MODE  RESTRICTED

---------- ------------------------------ ---------- ----------

         2 PDB$SEED                       READ ONLY  NO

         3 SRLABPLUG                      READ WRITE NO



SQL> alter system set "_exadata_feature_on"=true scope=spfile;


System altered.


SQL> shut immediate;

Database closed.

Database dismounted.

ORACLE instance shut down.

SQL> startup

ORACLE instance started.


Total System Global Area 1795161192 bytes

Fixed Size                  9137256 bytes

Variable Size             520093696 bytes

Database Buffers         1258291200 bytes

Redo Buffers                7639040 bytes

Database mounted.

Database opened.

SQL> show pdbs


    CON_ID CON_NAME                       OPEN MODE  RESTRICTED

---------- ------------------------------ ---------- ----------

         2 PDB$SEED                       READ ONLY  NO

         3 SRLABPLUG                      READ WRITE NO

SQL> show parameter _exadata_feature_on


NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

_exadata_feature_on                  boolean     TRUE

SQL> delete table myobjects;

delete table myobjects

       *

ERROR at line 1:

ORA-00903: invalid table name



SQL> delete myobjectsl

  2

SQL>

SQL> clear screen

SQL> show pdbs


    CON_ID CON_NAME                       OPEN MODE  RESTRICTED

---------- ------------------------------ ---------- ----------

         2 PDB$SEED                       READ ONLY  NO

         3 SRLABPLUG                      READ WRITE NO

SQL> show parameter _exadata


NAME                                 TYPE        VALUE

------------------------------------ ----------- ------------------------------

_exadata_feature_on                  boolean     TRUE

SQL> delete myobjects;


579064 rows deleted.


SQL> commit;


Commit complete.


SQL> select count(*) from myobjects;


  COUNT(*)

----------

         0


SQL> insert into myobjects select * from myobjects;


0 rows created.


SQL> insert into myobjects select * from dba_objects;


72394 rows created.


SQL> commit;


Commit complete.


SQL> select distinct object_type from myobjects where owner ='SYS';


OBJECT_TYPE

-----------------------

INDEX

JOB

TABLE PARTITION

SYNONYM

JAVA DATA

RULE SET

SCHEDULE

CLUSTER

PACKAGE BODY

JAVA CLASS

TRIGGER

TYPE BODY

RULE

EDITION

PROCEDURE

CONTEXT

EVALUATION CONTEXT

PACKAGE

DIRECTORY

DESTINATION

CONSUMER GROUP

RESOURCE PLAN

TABLE SUBPARTITION

INDEX PARTITION

DATABASE LINK

LIBRARY

FUNCTION

JOB CLASS

QUEUE

PROGRAM

UNDEFINED

CONTAINER

LOB

TYPE

SEQUENCE

JAVA SOURCE

OPERATOR

SCHEDULER GROUP

WINDOW

UNIFIED AUDIT POLICY

TABLE

VIEW

JAVA RESOURCE

LOB PARTITION


44 rows selected.


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


PLAN_TABLE_OUTPUT

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

SQL_ID  7696ty1tpy05b, child number 0

-------------------------------------

select distinct object_type from myobjects where owner ='SYS'


Plan hash value: 2299144702


----------------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |

----------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |       |       |       |  4371 (100)|          |

|   1 |  HASH UNIQUE       |           | 72527 |  5595K|  6320K|  4371   (1)| 00:00:01 |

|*  2 |   TABLE ACCESS FULL| MYOBJECTS | 72527 |  5595K|       |  3038   (1)| 00:00:01 |

----------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   2 - filter("OWNER"='SYS')


Note

-----

   - dynamic statistics used: dynamic sampling (level=2)



23 rows selected.


SQL> insert into myobjects select * from myobjects;


72394 rows created.


SQL> commit;


Commit complete.


SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format=>'TYPICAL'));


PLAN_TABLE_OUTPUT

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

SQL_ID  4238v4v9f7xmq, child number 0

-------------------------------------

insert into myobjects select * from myobjects


Plan hash value: 3078646338


--------------------------------------------------------------------------------------

| Id  | Operation                | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------------------

|   0 | INSERT STATEMENT         |           |       |       |  3037 (100)|          |

|   1 |  LOAD TABLE CONVENTIONAL | MYOBJECTS |       |       |            |          |

|   2 |   TABLE ACCESS FULL      | MYOBJECTS |     1 |   481 |  3037   (1)| 00:00:01 |

--------------------------------------------------------------------------------------


Note

-----

   - dynamic statistics used: dynamic sampling (level=2)



18 rows selected.


SQL> EXEC DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO;


PL/SQL procedure successfully completed.


SQL> alter system flush shared_pool;


System altered.


SQL> select distinct object_type from myobjects where owner='SYS';


OBJECT_TYPE

-----------------------

INDEX

JOB

CLUSTER

SYNONYM

TABLE PARTITION

SCHEDULE

RULE SET

JAVA DATA

EDITION

PACKAGE BODY

TYPE BODY

TRIGGER

RULE

JAVA CLASS

DIRECTORY

PACKAGE

PROCEDURE

CONTEXT

TABLE SUBPARTITION

CONSUMER GROUP

RESOURCE PLAN

DESTINATION

EVALUATION CONTEXT

FUNCTION

LIBRARY

INDEX PARTITION

JOB CLASS

QUEUE

DATABASE LINK

UNDEFINED

PROGRAM

SEQUENCE

LOB

CONTAINER

TYPE

OPERATOR

WINDOW

SCHEDULER GROUP

JAVA SOURCE

TABLE

VIEW

LOB PARTITION

UNIFIED AUDIT POLICY

JAVA RESOURCE


44 rows selected.


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


PLAN_TABLE_OUTPUT

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

SQL_ID  f7q50yk2vms83, child number 0

-------------------------------------

select distinct object_type from myobjects where owner='SYS'


Plan hash value: 2299144702


----------------------------------------------------------------------------------------

| Id  | Operation          | Name      | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |

----------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT   |           |       |       |       |  4933 (100)|          |

|   1 |  HASH UNIQUE       |           |   103K|  7954K|  8976K|  4933   (1)| 00:00:01 |

|*  2 |   TABLE ACCESS FULL| MYOBJECTS |   103K|  7954K|       |  3038   (1)| 00:00:01 |

----------------------------------------------------------------------------------------


Predicate Information (identified by operation id):

---------------------------------------------------


   2 - filter("OWNER"='SYS')


Note

-----

   - dynamic statistics used: dynamic sampling (level=2)



23 rows selected.


SQL> SELECT NUM_ROWS, BLOCKS, NOTES FROM  DBA_TAB_STATISTICS WHERE  TABLE_NAME = 'MYOBJECTS';


  NUM_ROWS     BLOCKS NOTES

---------- ---------- -------------------------



SQL> select tablespace_name from dba_tablespaces;


TABLESPACE_NAME

------------------------------

SYSTEM

SYSAUX

UNDOTBS1

TEMP

USERS


SQL> alter session set container=SRLABPLUG;


Session altered.


SQL> show pdbs


    CON_ID CON_NAME                       OPEN MODE  RESTRICTED

---------- ------------------------------ ---------- ----------

         3 SRLABPLUG                      READ WRITE NO

SQL>

SQL> CREATE USER testuser1 IDENTIFIED BY testuser1

  QUOTA UNLIMITED ON users;  2


User created.


SQL> GRANT CREATE SESSION, CREATE TABLE TO testuser1;


Grant succeeded.


SQL> GRANT CREATE PROCEDURE TO testuser1;


Grant succeeded.


SQL> GRANT SELECT_CATALOG_ROLE TO testuser1;


Grant succeeded.


SQL> GRANT ANALYZE ANY TO testuser1;


Grant succeeded.


SQL> CONN c##testuser1/testuser1@//192.168.58.101:1521/SRLABPLUG

ERROR:

ORA-01017: invalid username/password; logon denied



Warning: You are no longer connected to ORACLE.

SQL>

SQL>

SQL> conn / as sysdba

Connected.

SQL> conn testuser1/testuser1@//192.168.58.101:1521/SRLABPLUG

Connected.

SQL> show user

USER is "TESTUSER1"

SQL> CREATE TABLE tab1 (

  id           NUMBER,

  description  VARCHAR2(50)

);  2    3    4


Table created.


SQL> CREATE OR REPLACE PROCEDURE reset_tab1 AS

BEGIN

  EXECUTE IMMEDIATE 'TRUNCATE TABLE tab1';

  INSERT /*+ APPEND */ INTO tab1 (id, description)

    SELECT level, 'Description of ' || level

    FROM   dual

    CONNECT BY level <= 1000;

  COMMIT;

  DBMS_STATS.gather_table_stats(NULL, 'tab1');

END;

/  2    3    4    5    6    7    8    9   10   11


Procedure created.


SQL> EXEC reset_tab1;


PL/SQL procedure successfully completed.


SQL> EXEC DBMS_STATS.flush_database_monitoring_info;


PL/SQL procedure successfully completed.


SQL> -- SQL*Plus and SQLcl formatting.

SET LINESIZE 100 PAGESIZE 1000

COLUMN table_name FORMAT A10

COLUMN partition_name FORMAT A15

COLUMN column_name FORMAT A11

COLUMN notes FORMAT A25

COLUMN high_value FORMAT 9999999999

COLUMN low_value FORMAT 9999999999



-- NUM_ROWS from USER_TABLES.

SELECT table_name,

       num_rows

FROM   user_tables

WHERE  table_name = 'TAB1';SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL> SQL>   2    3    4


TABLE_NAME   NUM_ROWS

---------- ----------

TAB1             1000


SQL>

-- NUM_ROWS from USER_TAB_STATISTICS.

SELECT table_name,

       num_rows,

       notes

FROM   user_tab_statistics

WHERE  table_name = 'TAB1';SQL> SQL>   2    3    4    5


TABLE_NAME   NUM_ROWS NOTES

---------- ---------- -------------------------

TAB1             1000


SQL>

-- LOW_VALUE and HIGH_VALUE of ID column from USER_TAB_COL_STATISTICS.

SELECT table_name,

       column_name,

       UTL_RAW.cast_to_number(low_value) AS low_value,

       UTL_RAW.cast_to_number(high_value) AS high_value,

       sample_size,

       notes

FROM   user_tab_col_statistics

WHERE  table_name = 'TAB1'

AND    column_name = 'ID';SQL> SQL>   2    3    4    5    6    7    8    9


TABLE_NAME COLUMN_NAME   LOW_VALUE  HIGH_VALUE SAMPLE_SIZE NOTES

---------- ----------- ----------- ----------- ----------- -------------------------

TAB1       ID                    1        1000        1000


SQL> -- Reset the test data.

EXEC reset_tab1;SQL>


PL/SQL procedure successfully completed.


SQL> -- Conventional path insert. (rows 1001-2000)

INSERT INTO tab1

SELECT level+1000 AS id,

       'Description of ' || level AS description

FROM   dual

CONNECT BY level <= 1000;

COMMIT;SQL>   2    3    4    5

1000 rows created.


SQL>


Commit complete.


SQL> SELECT * FROM TABLE(DBMS_XPLAN.display_cursor);


PLAN_TABLE_OUTPUT

----------------------------------------------------------------------------------------------------

SQL_ID  5w9a7t69pzwcj, child number 0

-------------------------------------

INSERT INTO tab1 SELECT level+1000 AS id,        'Description of ' ||

level AS description FROM   dual CONNECT BY level <= 1000


Plan hash value: 1236776825


---------------------------------------------------------------------------------

| Id  | Operation                        | Name | Rows  | Cost (%CPU)| Time     |

---------------------------------------------------------------------------------

|   0 | INSERT STATEMENT                 |      |       |     2 (100)|          |

|   1 |  LOAD TABLE CONVENTIONAL         | TAB1 |       |            |          |

|   2 |   OPTIMIZER STATISTICS GATHERING |      |     1 |     2   (0)| 00:00:01 |

|   3 |    CONNECT BY WITHOUT FILTERING  |      |       |            |          |

|   4 |     FAST DUAL                    |      |     1 |     2   (0)| 00:00:01 |

---------------------------------------------------------------------------------



17 rows selected.


SQL> EXEC DBMS_STATS.flush_database_monitoring_info;


PL/SQL procedure successfully completed.


SQL> -- NUM_ROWS from USER_TABLES.

SELECT table_name,

       num_rows

FROM   user_tables

WHERE  table_name = 'TAB1';

SQL>   2    3    4

TABLE_NAME   NUM_ROWS

---------- ----------

TAB1             1000


SQL> -- NUM_ROWS from USER_TAB_STATISTICS.

SELECT table_name,

       num_rows,

       notes

FROM   user_tab_statistics

WHERE  table_name = 'TAB1';SQL>   2    3    4    5


TABLE_NAME   NUM_ROWS NOTES

---------- ---------- -------------------------

TAB1             1000

TAB1             2000 STATS_ON_CONVENTIONAL_DML


SQL> -- LOW_VALUE and HIGH_VALUE of ID column from USER_TAB_COL_STATISTICS.

SELECT table_name,

       column_name,

       UTL_RAW.cast_to_number(low_value) AS low_value,

       UTL_RAW.cast_to_number(high_value) AS high_value,

       sample_size,

       notes

FROM   user_tab_col_statistics

WHERE  table_name = 'TAB1'

AND    column_name = 'ID';SQL>   2    3    4    5    6    7    8    9


TABLE_NAME COLUMN_NAME   LOW_VALUE  HIGH_VALUE SAMPLE_SIZE NOTES

---------- ----------- ----------- ----------- ----------- -------------------------

TAB1       ID                    1        1000        1000

TAB1       ID                    1        1506           9 STATS_ON_CONVENTIONAL_DML


Cheers!

Ramesh.


Please follow me on: DBA BLOG:https://www.sachinrameshoracledbablog.com YOUTUBE: https://www.youtube.com/oraclef1 TWITTER: https://twitter.com/sachinrameshdba FACEBOOK: https://www.facebook.com/rameshkumar.krishnamoorthy.9 LINKEDIN:https://www.linkedin.com/in/ramesh-kumar-krishnamoorthy-3a67ba69 INSTAGRAM: https://www.instagram.com/oraclef1 Email: oraclehelplines@gmail.com





Post a Comment: