Monday, November 19, 2012

Cardinality Estimate

When it comes to SQL Tuning, It always starts with an execution plan, likewise this post is no different. I normally suggest to avoid SQL's and advice to write them only when required. The best way to tune an SQL  is to avoid writing it, but in reality this can't happen.

Lets see how we can avoid writing SQL rather saying it as avoid executing SQL.

The below is a small piece which i have done in my recent tuning projects  

SQL> CREATE TABLE tblchk
  2  (
  3    id number,
  4    id_val varchar2(50)
  5  );

Table created.

Elapsed: 00:00:00.30
SQL> 
SQL> 
SQL> declare
  2  type t1 is table of tblchk%rowtype;
  3  v_t1 t1:=t1();
  4  x number:= 65;
  5  begin
  6  
  7  for i in 1..20000 loop
  8  v_t1.extend;
  9  v_t1(i).id:=i;
 10  v_t1(i).id_val:=chr(x);
 11  x:=x+1;
 12  if x=68 then
 13  x:=65;
 14  end if;
 15  end loop;
 16  
 17  forall j in v_t1.first..v_t1.last
 18  insert into tblchk values v_t1(j);
 19  
 20  commit;
 21  end;
 22  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.27
SQL> 
SQL> exec dbms_stats.gather_table_stats('HARI','TBLCHK');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.51
SQL> 
SQL> select count(*) from tblchk;

  COUNT(*)                                               
----------                                               
     20000                                               

Elapsed: 00:00:00.00

Lets query the table
SQL> alter system flush shared_pool;

System altered.

Elapsed: 00:00:00.34
SQL> 
SQL> variable a varchar2(10);
SQL> 
SQL> exec :a:='ORACLE';
SQL> set autotrace traceonly
SQL>
SQL> 
SQL> select * from tblchk where id_val = :a;

no rows selected

Elapsed: 00:00:00.11

Execution Plan
----------------------------------------------------------                  
Plan hash value: 729812915                                                  
                                                                            
----------------------------------------------------------------------------
| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |        |  6667 | 40002 |    11   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TBLCHK |  6667 | 40002 |    11   (0)| 00:00:01 |
----------------------------------------------------------------------------
                                                                            
Predicate Information (identified by operation id):                         
---------------------------------------------------                         
                                                                            
   1 - filter("ID_VAL"=:A)                                                  


Statistics
----------------------------------------------------
        111  recursive calls                        
          0  db block gets                          
         51  consistent gets                        
         37  physical reads                         
          0  redo size                              
        342  bytes sent via SQL*Net to client       
        408  bytes received via SQL*Net from client 
          1  SQL*Net roundtrips to/from client      
          0  sorts (memory)                         
          0  sorts (disk)                           
          0  rows processed                         


"No rows selected" but why is the Rows column in execution plan showing it as 6667. Although we can get this fixed with a much similar approach as what we have seen in my earlier article on extended stats but now am looking for other options, 

Lets start with TKprof. 
select * 
from
 tblchk where id_val = :a1


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.07          0          0          0           0
Execute      1      0.00       0.00          0          0          0           0
Fetch        1      0.00       0.12         36         38          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        3      0.00       0.19         36         38          0           0

Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 85  

Rows     Row Source Operation
-------  ---------------------------------------------------
      0  TABLE ACCESS FULL TBLCHK (cr=38 pr=36 pw=0 time=0 us cost=11 size=40002 card=6667)


As expected the row source operation shows a cardinality as 6667. So how does oracle estimate the cardinality as 6667


SQL> select round(count(*)/count(distinct id_val)) ROWS_VAL from tblchk;

  ROWS_VAL
----------
      6667

Elapsed: 00:00:00.02
SQL>

Well, you can also cross check the actual's with the estimated rows


SQL> set linesize 150
SQL> set pagesize 150
SQL> 
SQL> SELECT * FROM   TABLE(DBMS_XPLAN.DISPLAY_CURSOR('96vjj2ncju5p5',null,'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT                                                                               
------------------------------------------------------------------------------------------------
SQL_ID  96vjj2ncju5p5, child number 0                                                           
-------------------------------------                                                           
select * from tblchk where id_val = :a1                                                         
                                                                                                
Plan hash value: 729812915                                                                      
                                                                                                
----------------------------------------------------------------------------------------------- 
| Id  | Operation         | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | 
----------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT  |        |      1 |        |      0 |00:00:00.03 |      38 |     36 | 
|*  1 |  TABLE ACCESS FULL| TBLCHK |      1 |   6667 |      0 |00:00:00.03 |      38 |     36 | 
----------------------------------------------------------------------------------------------- 
                                                                                                
Predicate Information (identified by operation id):                                             
---------------------------------------------------                                             
                                                                                                
   1 - filter("ID_VAL"=:A1)                                                                     


Lets provide more information to the optimizer so as to have a better cardinality estimate.


SQL>
SQL> alter table tblchk add CONSTRAINT check_idval CHECK (id_val in ('A','B','C'
));

Table altered.

Elapsed: 00:00:00.91
SQL>
SQL>
SQL> exec dbms_stats.gather_table_stats('HARI','TBLCHK');

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.27
SQL>
Will check constraint help me to find the correct cardinality ?, Soon will post more interesting facts on the same

Wednesday, October 3, 2012

Oracle 12c New Features

Oracle 12c New Features  

Compression - Auto compression with Heat Maps / Automatic Data Optimization

        This is for the first time in database mgmt, we gonna have intelligent compression. With the metadata in hand, oracle let's us know how is my data used by the applications. Based on my data usage heat maps are generated which are classified as

  • Hot       -   Active Modifications - Last modified is less than a week's time.
  • Warm  -   Moderate Modifications - Last modified is greater than a week and less than a Year.
  • Cold    -   Nill - Last modified is greater than a year


When I look into a tablespace which is holding the tables shown in the picture (Heat Maps), Oracle 12c provides a clear data usage differentiation through heat maps. The EMP table is under HOT section as it is subject to modifications. But when we drill down into EMP table and look into its partitions Oracle 12c provides data usage details across partitions. 













Using these info, automatic data optimization creates policies to perform automatic data compression.



The compression levels are 
3x   - Advanced Row Compression
10x - Columnar Query Compression
15x - Columar Archive Compression

        Now with these advanced levels of compression we can load more data into memory, so does that mean are we heading towards in-memory database in Oracle 12c? Lots more features to be explored, watch out in this space :)




















Disclaimer - The views expressed are my own and not necessarily those of Oracle  and its affiliates or whom so ever.

Monday, October 1, 2012

Zero Downtime Migration using Oracle GoldenGate


               It's all about migrating data from one environment to another. This blog is about one way replication, the simplest of its kind. Data is replicated from the source to the target database in an uni-directional format. All sorts of modifications are done only at the source and the same would be replicated to the target system.  Only Migration is covered below and will add fallback options in forth coming blogs.

Obtaining sufficient downtime is always a challenge and Oracle GoldenGate (OGG) comes here for our rescue. The real challenge in migration is when the end users are allowed to perform modifications to the system until the migration cutover. The below set of steps explains the Oracle GoldenGate execution flow for Zero Downtime Migration in multiple steps.

               As a first step let's create a source environment which has got continuous data modifications,  
SQL> REM CREATING SOURCE AND TARGET TABLE
SQL> 
SQL> create table GG_SRC as select * from dba_objects;

Table created.

Elapsed: 00:00:00.54
SQL> ALTER TABLE GG_SRC add CONSTRAINT PK_GG_SRC PRIMARY KEY (object_id);

Table altered.

Elapsed: 00:00:00.36
SQL> 
SQL> 
SQL> create table GG_TRGT as select * from dba_objects where 1 = 2;

Table created.

Elapsed: 00:00:00.03
SQL> 
SQL> ALTER TABLE GG_TRGT add CONSTRAINT PK_GG_TRGT PRIMARY KEY (object_id);

Table altered.

Elapsed: 00:00:00.01
SQL> 
SQL> exec dbms_stats.gather_table_stats('HARI','GG_SRC');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.47
-------------

Creating procedures to perform data modifications, later these are scheduled as an on-going process
SQL - INSERT - Stored Procedure to Insert 20 records scheduled to execute every minute
create or replace
PROCEDURE sp_ins_gg AS
BEGIN
  INSERT
  INTO GG_SRC(object_id,   object_name,subobject_name,data_object_id,object_type,created) WITH temp_max AS
    (SELECT MAX(object_id) AS
    max_id
     FROM GG_SRC)
  SELECT max_id + rownum AS
  object_id,
  'ORACLE' ||(max_id + rownum)||chr(rownum+60) AS object_name,
  'ORACLE' ||(max_id + rownum)||chr(rownum+60) AS subobject_name,
  rownum AS data_object_id,
    'ORACLE' ||(max_id + rownum) AS object_type,
    sysdate AS created
  FROM temp_max CONNECT BY LEVEL < 21;
  COMMIT;
END sp_ins_gg;
/

Scheduling 20 INSERT's Every Minute
begin  
 -- Every minute - Every day  
 dbms_scheduler.create_schedule(  
 schedule_name  => 'MIN_BY_MIN',  
 start_date    => trunc(sysdate),  
 repeat_interval => 'freq=MINUTELY;interval=1',  
 comments     => 'Execute: ALL DAY EVERY MINUTE');     
end;  
/


begin  
-- Invoke the Procedure   
dbms_scheduler.create_program  
(program_name=> 'GG_INSERT',  
 program_type=> 'STORED_PROCEDURE',  
 program_action=> 'sp_ins_gg',  
 enabled=>true,  
 comments=>'Procedure to insert source data'  
 );  
end; 
/

begin  
-- Create the Job  
dbms_scheduler.create_job  
 (job_name => 'JOB_GG_INSERT',  
  program_name=> 'GG_INSERT',  
  schedule_name=>'MIN_BY_MIN',  
  enabled=>true,  
  auto_drop=>false,  
  comments=>'Job to insert source data every minute');  
end;  
/

SQL - UPDATE - Stored Procedure to update two records is scheduled to execute every 10 seconds
create or replace
PROCEDURE sp_upd_gg AS
BEGIN

  UPDATE GG_SRC
  SET subobject_name = 'ORACLE'
  WHERE subobject_name IS NULL
  AND rownum < 2;
  
  UPDATE GG_SRC
  SET object_type = 'ORACLE'
  WHERE owner IS NULL
  AND rownum < 2;
  
  COMMIT;
END sp_upd_gg;
/

Scheduling 2 UPDATEs Every 10 Seconds

begin  
 -- Every 10 seconds - Every day  
 dbms_scheduler.create_schedule(  
 schedule_name  => 'SEC_10',  
 start_date    => trunc(sysdate),  
 repeat_interval => 'FREQ=SECONDLY; INTERVAL=10',  
 comments     => 'Execute: ALL DAY EVERY MINUTE');     
end;  
/

begin  
-- Invoke the Procedure   
dbms_scheduler.create_program  
(program_name=> 'GG_UPDATE',  
 program_type=> 'STORED_PROCEDURE',  
 program_action=> 'sp_upd_gg',  
 enabled=>true,  
 comments=>'Procedure to insert source data'  
 );  
end; 
/

begin  
-- Create the Job  
dbms_scheduler.create_job  
 (job_name => 'JOB_GG_UPDATE',  
  program_name=> 'GG_UPDATE',  
  schedule_name=>'SEC_10',  
  enabled=>true,  
  auto_drop=>false,  
  comments=>'Job to update source data every 10 seconds');  
end;  
/

SQL - DELETE - Stored Procedure to delete two records is scheduled to execute every 15 seconds
create or replace
PROCEDURE sp_del_gg AS
BEGIN

  DELETE FROM GG_SRC
  WHERE object_id =
    (SELECT object_id
     FROM GG_SRC
     WHERE subobject_name != 'ORACLE'
     AND rownum < 2);

  DELETE FROM GG_SRC
  WHERE object_id =
    (SELECT object_id
     FROM GG_SRC
     WHERE object_type != 'TABLE'
     AND rownum < 2);

  COMMIT;

END sp_del_gg;
/

Scheduling 2 DELETEs Every 15 Seconds


begin  
 -- Every 15 seconds - Every day  
 dbms_scheduler.create_schedule(  
 schedule_name  => 'SEC_15',  
 start_date    => trunc(sysdate),  
 repeat_interval => 'FREQ=SECONDLY; INTERVAL=15',  
 comments     => 'Execute: ALL DAY EVERY MINUTE');     
end;  
/

begin  
-- Invoke the Procedure   
dbms_scheduler.create_program  
(program_name=> 'GG_DELETE',  
 program_type=> 'STORED_PROCEDURE',  
 program_action=> 'sp_del_gg',  
 enabled=>true,  
 comments=>'Procedure to insert source data'  
 );  
end; 
/

begin  
-- Create the Job  
dbms_scheduler.create_job  
 (job_name => 'JOB_GG_DELETE',  
  program_name=> 'GG_DELETE',  
  schedule_name=>'SEC_15',  
  enabled=>true,  
  auto_drop=>false,  
  comments=>'Job to delete source data every 15 seconds');  
end;  
/


Lets check the number records in source and target table

SQL> SELECT SYSTIMESTAMP FROM DUAL;

SYSTIMESTAMP                                                                                 
---------------------------------------------------------------------------                  
05-OCT-12 12.34.43.028000 AM +05:30                                                          

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) as SOURCE_COUNT FROM GG_SRC;

SOURCE_COUNT                                                                                 
------------                                                                                 
       72287                                                                                 

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) as TARGET_COUNT FROM GG_TRGT;

TARGET_COUNT                                                                                 
------------                                                                                 
           0                                                                                 

Elapsed: 00:00:00.00
SQL>
SQL> --REM "Checking UPDATE RECORDS COUNT"---
SQL> SELECT COUNT(*) as Source_subobject_count FROM GG_SRC WHERE subobject_name = 'ORACLE';

SOURCE_SUBOBJECT_COUNT                                                                       
----------------------                                                                       
                    79                                                                       

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) as Target_subobject_count FROM GG_TRGT WHERE subobject_name = 'ORACLE';

TARGET_SUBOBJECT_COUNT                                                                       
----------------------                                                                       
                     0                                                                       

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) as Source_object_type FROM GG_SRC WHERE object_type = 'ORACLE';

SOURCE_OBJECT_TYPE                                                                           
------------------                                                                           
                 4                                                                           

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) as Target_object_type FROM GG_TRGT WHERE object_type = 'ORACLE';

TARGET_OBJECT_TYPE                                                                           
------------------                                                                           
                 0                                                                           

Elapsed: 00:00:00.00
SQL> 
SQL> REM--"Checking DELETE RECORDS COUNT"--
SQL> SELECT COUNT(*) FROM GG_SRC WHERE subobject_name != 'ORACLE';

  COUNT(*)                                                                                   
----------                                                                                   
       603                                                                                   

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) FROM GG_TRGT WHERE subobject_name != 'ORACLE';

  COUNT(*)                                                                                   
----------                                                                                   
         0                                                                                   

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) FROM GG_SRC WHERE object_type != 'TABLE';

  COUNT(*)                                                                                   
----------                                                                                   
     69487                                                                                   

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) FROM GG_TRGT WHERE object_type != 'TABLE';

  COUNT(*)                                                                                   
----------                                                                                   
         0                                                                                   

Elapsed: 00:00:00.00
SQL> 

Now the environment is set, let’s start Oracle GoldenGate. As a very first step start GoldenGate Manager Process in both Source and Target

Start GoldenGate Manager Process@SOURCE
Oracle GoldenGate Command Interpreter for Oracle
Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
Windows (optimized), Oracle 11g on Oct  5 2011 00:50:57

Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.

GGSCI (HARI-PC) 1> start mgr

Manager started.

--------------------------------------------------------------------
                 /*  SOURCE MANAGER RUN TIME INFO */ 
--------------------------------------------------------------------
***********************************************************************
                 Oracle GoldenGate Manager for Oracle
     Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
        Windows (optimized), Oracle 11g on Oct  5 2011 00:28:27

Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.


                    Starting at 2012-10-05 00:35:13
***********************************************************************

Operating System Version:
Microsoft Windows 7 , on x86
Version 6.1 (Build 7600: )

Process id: 3308

Parameters...

PORT 1350
DYNAMICPORTLIST 12010-12030,1350,1230


***********************************************************************
**                     Run Time Messages                             **
***********************************************************************

2012-10-05 00:35:13  INFO    OGG-00983  Manager started (port 1350)

Start GoldenGate Manager Process@TARGET
Oracle GoldenGate Command Interpreter for Oracle
Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
Windows (optimized), Oracle 11g on Oct  5 2011 00:50:57
Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.

GGSCI (HARI-PC) 1> start mgr

Manager started.

--------------------------------------------------------------------
                 /*  TARGET MANAGER RUN TIME INFO */ 
--------------------------------------------------------------------
***********************************************************************
                 Oracle GoldenGate Manager for Oracle
     Version 11.1.1.1.2 OGGCORE_11.1.1.1.2_PLATFORMS_111004.2100
        Windows (optimized), Oracle 11g on Oct  5 2011 00:28:27
Copyright (C) 1995, 2011, Oracle and/or its affiliates. All rights reserved.

                    Starting at 2012-10-05 00:35:34
***********************************************************************
Operating System Version:
Microsoft Windows 7 , on x86
Version 6.1 (Build 7600: )

Process id: 3976

Parameters...

PORT 7809
***********************************************************************
**                     Run Time Messages                             **
***********************************************************************
2012-10-05 00:35:34  INFO    OGG-00983  Manager started (port 7809)

                     Its normally Initial Load which is the next step to start, but in scenario like above the key is to handle continuous data modifications coming in. So in a Zero downtime  replication (ZDR) the initial step is to capture the current activity in the source database and then start the initial load i.e

Step 1 - ZDR - Extract and Datapump

























OGG- Extract and Datapump

GGSCI (HARI-PC) 2> START EXTRACT OEXT1

Sending START request to MANAGER ...
EXTRACT OEXT1 starting


GGSCI (HARI-PC) 3> INFO ALL

Program     Status      Group       Lag           Time Since Chkpt

MANAGER     RUNNING
EXTRACT     STOPPED     ODPUMP1     00:00:00      00:19:10
EXTRACT     RUNNING     OEXT1       00:19:16      00:00:03


GGSCI (HARI-PC) 4> START EXTRACT ODPUMP1

Sending START request to MANAGER ...
EXTRACT ODPUMP1 starting


GGSCI (HARI-PC) 5> INFO ALL

Program     Status      Group       Lag           Time Since Chkpt

MANAGER     RUNNING
EXTRACT     RUNNING     ODPUMP1     00:00:00      00:19:19
EXTRACT     RUNNING     OEXT1       00:00:00      00:00:02


Runtime Logs
                    /*  Extract  - Runtime Info */
***********************************************************************
**                     Run Time Messages                             **
***********************************************************************


2012-10-05 00:39:17  INFO    OGG-01517  Position of first record processed Seque
nce 160, RBA 19020816, SCN 0.4713843, Oct 3, 2012 12:56:36 AM.
TABLE resolved (entry HARI.GG_SRC):
  TABLE HARI.GG_SRC;

Using the following key columns for source table HARI.GG_SRC: OBJECT_ID.

                   /*  Datapump  - Runtime Info */
***********************************************************************
**                     Run Time Messages                             **
***********************************************************************

Opened trail file I:\OGG\SOURCE\dirdat\EX000014 at 2012-10-05 00:39:37

Switching to next trail file I:\OGG\SOURCE\dirdat\EX000015 at 2012-10-05 00:39:3
7 due to EOF, with current RBA 196334
Opened trail file I:\OGG\SOURCE\dirdat\EX000015 at 2012-10-05 00:39:37

TABLE resolved (entry HARI.GG_SRC):
  TABLE HARI.GG_SRC;
PASSTHRU mapping resolved for source table HARI.GG_SRC


Ok, my Extract and Datapump are running. But are they capturing the current modifications occurring at source , Let's check that 

 
GGSCI (HARI-PC) 13> stats extract oext1

Sending STATS request to EXTRACT OEXT1 ...

Start of Statistics at 2012-10-05 00:40:06.

Output to I:\OGG\SOURCE\dirdat\EX:

Extracting from HARI.GG_SRC to HARI.GG_SRC:

*** Total statistics since 2012-10-05 00:39:27 ***
        Total inserts                              520.00
        Total updates                              301.00
        Total deletes                              202.00
        Total discards                               0.00
        Total operations                          1023.00

*** Daily statistics since 2012-10-05 00:39:27 ***
        Total inserts                              520.00
        Total updates                              301.00
        Total deletes                              202.00
        Total discards                               0.00
        Total operations                          1023.00

*** Hourly statistics since 2012-10-05 00:39:27 ***
        Total inserts                              520.00
        Total updates                              301.00
        Total deletes                              202.00
        Total discards                               0.00
        Total operations                          1023.00

*** Latest statistics since 2012-10-05 00:39:27 ***
        Total inserts                              520.00
        Total updates                              301.00
        Total deletes                              202.00
        Total discards                               0.00
        Total operations                          1023.00

End of Statistics.

Yes the data modifications are captured by the Extract process. Have got sufficient number of DML's as of now. While this is running, lets start the next step 

Step 2 - ZDR - Initial Load



As a part of Initial Load the Target tables are synchronized with that of source. OGG takes the snapshot of commited transaction as of the start time of the Inital Load and migrates that data alone. 











OGG - Initial Load

This is gonna be a special run as it will be executed only once, 

GGSCI (HARI-PC) 15> start extract iext1

Sending START request to MANAGER ...
EXTRACT IEXT1 starting

/* From the Logs */ 
GGSCI command (HARI): start extract iext1.
Command received from GGSCI on host 192.168.1.2 (START EXTRACT IEXT1 ).
EXTRACT IEXT1 starting.
Wildcard resolution set to IMMEDIATE because SOURCEISTABLE is used.
EXTRACT IEXT1 starting.
EXTRACT IEXT1 started.
EXTRACT IEXT1 stopped normally.

The initial load extract process would start and stop its respective replicate process automatically. Now while the initial is running lets check the target database to validate if there are any data load,


SQL> SELECT SYSTIMESTAMP FROM DUAL;

SYSTIMESTAMP                                      
--------------------------------------------------
05-OCT-12 12.41.03.718000 AM +05:30               

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) as SOURCE_COUNT FROM GG_SRC;

SOURCE_COUNT                                      
------------                                      
       72375                                      

Elapsed: 00:00:00.01
SQL> SELECT COUNT(*) as TARGET_COUNT FROM GG_TRGT
  2  ;

TARGET_COUNT                                      
------------                                      
       72359                                      

Elapsed: 00:00:00.01
SQL> 
SQL> 
SQL> SELECT COUNT(*) as TARGET_COUNT FROM GG_TRGT;

TARGET_COUNT                                      
------------                                      
       72359                                      

Elapsed: 00:00:00.01

Let's also check the stats of initial load avaliable at OGG
GGSCI (HARI-PC) 15> info extract iext1

EXTRACT    IEXT1     Last Started 2012-10-05 00:40   Status STOPPED
Checkpoint Lag       Not Available
Log Read Checkpoint  Table HARI.GG_SRC
                     2012-10-05 00:41:01  Record 72359
Task                 SOURCEISTABLE


Since there are continuous changes happening in the database, let compare the records count in source and target again
SQL> SELECT SYSTIMESTAMP FROM DUAL;

SYSTIMESTAMP                                        
----------------------------------------------------
05-OCT-12 12.41.48.520000 AM +05:30                 

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) as SOURCE_COUNT FROM GG_SRC;

SOURCE_COUNT                                        
------------                                        
       72369                                        

Elapsed: 00:00:00.00
SQL> SELECT COUNT(*) as TARGET_COUNT FROM GG_TRGT;

TARGET_COUNT                                        
------------                                        
       72359                                        

Elapsed: 00:00:00.01
SQL> 

The last step is to start the replicate process at the target so as so apply the ongoing changes

Step 3 - ZDR - Start Replicate Process
























The replicate process picks the remote trail files and applies them on the target database.

OGG - Replicate Process
GGSCI (HARI-PC) 2> start replicat orep1

Sending START request to MANAGER ...
REPLICAT OREP1 starting


/*-------------------------------------------------------------------*/
***********************************************************************
**                     Run Time Messages                             **
***********************************************************************

Opened trail file I:\OGG\TARGET\dirdat\RT000012 at 2012-10-05 00:42:30

Switching to next trail file I:\OGG\TARGET\dirdat\RT000013 at 2012-10-05 00:42:3
0 due to EOF, with current RBA 196899
Opened trail file I:\OGG\TARGET\dirdat\RT000013 at 2012-10-05 00:42:30

Processed extract process graceful restart record at seq 13, rba 1426.
Processed extract process graceful restart record at seq 13, rba 1488.

MAP resolved (entry HARI.GG_SRC):
  MAP HARI.GG_SRC, TARGET  hari.GG_TRGT;
Using following columns in default map by name:
  OWNER, OBJECT_NAME, SUBOBJECT_NAME, OBJECT_ID, DATA_OBJECT_ID,
  OBJECT_TYPE, CREATED, LAST_DDL_TIME, TIMESTAMP, STATUS, TEMPORARY,
  GENERATED, SECONDARY, NAMESPACE, EDITION_NAME

Using the following key columns for target table HARI.GG_TRGT: OBJECT_ID.

Applying Lagging Transactions
GGSCI (HARI-PC) 5> send replicat orep1 getlag

Sending GETLAG request to REPLICAT OREP1 ...
Last record lag: 7 seconds.
At EOF, no more records to process.


Did OGG handle Collisions at the target since Extract process is started prior to Initial Load ?

GGSCI (HARI-PC) 6> stats replicat orep1

Sending STATS request to REPLICAT OREP1 ...

Start of Statistics at 2012-10-05 00:46:08.

Replicating from HARI.GG_SRC to HARI.GG_TRGT:

*** Total statistics since 2012-10-05 00:42:31 ***
        Total inserts                              631.00
        Total updates                              373.00
        Total deletes                              250.00
        Total discards                               0.00
        Total operations                          1254.00
        Total insert collisions                    391.00
        Total update collisions                     40.00
        Total delete collisions                     77.00

*** Daily statistics since 2012-10-05 00:42:31 ***
        Total inserts                              631.00
        Total updates                              373.00
        Total deletes                              250.00
        Total discards                               0.00
        Total operations                          1254.00
        Total insert collisions                    391.00
        Total update collisions                     40.00
        Total delete collisions                     77.00

*** Hourly statistics since 2012-10-05 00:42:31 ***
        Total inserts                              631.00
        Total updates                              373.00
        Total deletes                              250.00
        Total discards                               0.00
        Total operations                          1254.00
        Total insert collisions                    391.00
        Total update collisions                     40.00
        Total delete collisions                     77.00

*** Latest statistics since 2012-10-05 00:42:31 ***
        Total inserts                              631.00
        Total updates                              373.00
        Total deletes                              250.00
        Total discards                               0.00
        Total operations                          1254.00
        Total insert collisions                    391.00
        Total update collisions                     40.00
        Total delete collisions                     77.00

End of Statistics.

Yes OGG handles collisions and the same is obtained through stats. Let's validate the data in the database

SQL> 
SQL> EXEC DBMS_SCHEDULER.drop_job(job_name => 'JOB_GG_DELETE');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
SQL> EXEC DBMS_SCHEDULER.drop_job(job_name => 'JOB_GG_UPDATE');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.01
SQL> EXEC DBMS_SCHEDULER.drop_job(job_name => 'JOB_GG_INSERT');

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.03
SQL> 


Have stopped the scheduler jobs just like what we do during cutover/downtime. Let's check both source and target tables,

SQL> 
SQL> SELECT SYSTIMESTAMP FROM DUAL;

SYSTIMESTAMP                                                                                         
---------------------------------------------------------------------------                          
05-OCT-12 12.47.39.329000 AM +05:30                                                                  

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) as SOURCE_COUNT FROM GG_SRC;

SOURCE_COUNT                                                                                         
------------                                                                                         
       72445                                                                                         

Elapsed: 00:00:00.01
SQL> 
SQL> SELECT COUNT(*) as TARGET_COUNT FROM GG_TRGT;

TARGET_COUNT                                                                                         
------------                                                                                         
       72445                                                                                         

Elapsed: 00:00:00.00
SQL> 
SQL>  --REM "Checking UPDATE RECORDS COUNT"---
SQL> SELECT COUNT(*) as Source_subobject_count FROM GG_SRC WHERE subobject_name = 'ORACLE';

SOURCE_SUBOBJECT_COUNT                                                                               
----------------------                                                                               
                   155                                                                               

Elapsed: 00:00:00.01
SQL> 
SQL> SELECT COUNT(*) as Target_subobject_count FROM GG_TRGT WHERE subobject_name = 'ORACLE';

TARGET_SUBOBJECT_COUNT                                                                               
----------------------                                                                               
                   155                                                                               

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) as Source_object_type FROM GG_SRC WHERE object_type = 'ORACLE';

SOURCE_OBJECT_TYPE                                                                                   
------------------                                                                                   
                 4                                                                                   

Elapsed: 00:00:00.01
SQL> 
SQL> SELECT COUNT(*) as Target_object_type FROM GG_TRGT WHERE object_type = 'ORACLE';

TARGET_OBJECT_TYPE                                                                                   
------------------                                                                                   
                 4                                                                                   

Elapsed: 00:00:00.00
SQL> 
SQL> REM--"Checking DELETE RECORDS COUNT"--
SQL> SELECT COUNT(*) FROM GG_SRC WHERE subobject_name != 'ORACLE';

  COUNT(*)                                                                                           
----------                                                                                           
       761                                                                                           

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT COUNT(*) FROM GG_TRGT WHERE subobject_name != 'ORACLE';

  COUNT(*)                                                                                           
----------                                                                                           
       761                                                                                           

Elapsed: 00:00:00.01
SQL> 
SQL> SELECT COUNT(*) FROM GG_SRC WHERE object_type != 'TABLE';

  COUNT(*)                                                                                           
----------                                                                                           
     69645                                                                                           

Elapsed: 00:00:00.01
SQL> 
SQL> SELECT COUNT(*) FROM GG_TRGT WHERE object_type != 'TABLE';

  COUNT(*)                                                                                           
----------                                                                                           
     69645                                                                                           

Elapsed: 00:00:00.01
SQL> 
SQL> spool off


Thus we are able to migrate the data to the target table while the data modifications are still active at the source end. 

Wednesday, September 26, 2012

Extended Statistics in Oracle 11g

I was not really planning to write about extended stats, it's all started with Full table scan then was trying to replace full table scan with an index and finally ended with extended stats due to the wrong cardinality estimate :)

Before we get into oracle extended statistics let check few cardinality issues when my data is skewed.

Lets start with the below demo
SQL> CREATE TABLE tbl_fts_chk
  2    (
  3      id           NUMBER,
  4      id_val       VARCHAR2(10),
  5      location_val VARCHAR2(10)
  6    );

Table created.

Elapsed: 00:00:00.32
SQL> BEGIN
  2    FOR x IN 1..1000
  3    LOOP
  4      INSERT INTO tbl_fts_chk
  5      SELECT *
  6      FROM
  7        ( WITH tbl_temp AS
  8        (SELECT rownum AS id,
  9          'ORACLE'
 10          ||rownum AS id_val,
 11          'location_'
 12          ||chr(rownum+64) AS location_val
 13        FROM dual
 14          CONNECT BY level < 11
 15        ),
 16        tbl_temp1 AS
 17        (SELECT rownum AS id,
 18          'ORACLE'
 19          ||rownum AS id_val,
 20          'location_'
 21          ||chr(rownum+64) AS location_val
 22        FROM dual
 23          CONNECT BY level < 11
 24        ORDER BY id DESC
 25        )
 26      SELECT t1.id,
 27        t1.id_val,
 28        t1.location_val
 29      FROM tbl_temp t1,
 30        tbl_temp1 t2
 31      WHERE t1.id > t2.id
 32      UNION ALL
 33      SELECT t1.id,
 34        t1.id_val,
 35        t1.location_val
 36      FROM tbl_temp1 t1,
 37        tbl_temp1 t2
 38      WHERE t2.id > t1.id
 39        );
 40    END LOOP;
 41    COMMIT;
 42  END;
 43  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.77
SQL> select count(*) from tbl_fts_chk;

  COUNT(*)                                                                                                                                            
----------                                                                                                                                            
     90000                                                                                                                                            

Elapsed: 00:00:00.01
----------------
SQL> EXEC DBMS_STATS.gather_table_stats('HARI', 'TBL_FTS_CHK', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.23


    Note the estimate percent is AUTO SAMPLE SIZE. The below simple query triggers the doubt and lets check whether do we get any merit for investigation,
SQL> set autotrace traceonly
SQL> 
SQL> select * from tbl_fts_chk where id = 5 and id_val = 'ORACLE5';

9000 rows selected.

Elapsed: 00:00:00.12

Execution Plan
----------------------------------------------------------
Plan hash value: 2042300665

---------------------------------------------------------------------------------
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time    |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |             |   900 | 19800 |   103   (1)| 00:00:02 |
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |   900 | 19800 |   103   (1)| 00:00:02 |
---------------------------------------------------------------------------------

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

   1 - filter("ID"=5 AND "ID_VAL"='ORACLE5')


Statistics
----------------------------------------------------------
        275  recursive calls
          0  db block gets
       1005  consistent gets
        378  physical reads
          0  redo size
     121681  bytes sent via SQL*Net to client
       7008  bytes received via SQL*Net from client
        601  SQL*Net roundtrips to/from client
          5  sorts (memory)
          0  sorts (disk)
       9000  rows processed

   Why is my cardinality just 900 instead of being 9000. Which means it is calculated as 1 percent but actually it has to be 10 percent of total number of records (90,000).

The math here is

Step1 :
Column - ID         - Has 10 distinct Values
SQL> select count(*),ID from tbl_fts_chk group by ID;

  COUNT(*)         ID
---------- ----------
      9000          1
      9000          6
      9000          2
      9000          4
      9000          5
      9000          8
      9000          3
      9000          7
      9000          9
      9000         10

10 rows selected.

Elapsed: 00:00:00.23


Column - ID_Val  - Has 10 distinct Values
SQL> select count(*),ID_VAL from tbl_fts_chk group by ID_VAL;

  COUNT(*) ID_VAL
---------- ----------
      9000 ORACLE7
      9000 ORACLE8
      9000 ORACLE6
      9000 ORACLE2
      9000 ORACLE4
      9000 ORACLE10
      9000 ORACLE3
      9000 ORACLE5
      9000 ORACLE9
      9000 ORACLE1

10 rows selected.

Elapsed: 00:00:00.06


Step2
Given value for ID = 5 and for ID_Val = ORACLE5, which means 1 out of 10 is being retrieved out of total number of records

Step 3
Number Of records * Column ID selectivity * Column ID_Val Selectivity
90000 * .1 * .1 = 900

Oracle hasn't considered the actual repetition of data. I retrieve only 10 percent of the data. Can index help me here ?, lets check
SQL> create index idx_fts_chk1 on tbl_fts_chk(id,id_val);

Index created.

Elapsed: 00:00:00.25
SQL> EXEC DBMS_STATS.gather_table_stats('HARI', 'TBL_FTS_CHK', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.95

Executing the query again
SQL> variable a1 number;
SQL> exec :a1:=5;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> variable a2 varchar2(10);
SQL> 
SQL> exec :a2:='ORACLE5';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = :a1 AND T2.ID_VAL = :a2;

9000 rows selected.

Elapsed: 00:00:00.23

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |  9000 |   193K|   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |  9000 |   193K|   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID_VAL"=:A2 AND "T2"."ID"=TO_NUMBER(:A1))                                                                                         


Statistics
----------------------------------------------------------                                                                                            
        367  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
       1024  consistent gets                                                                                                                          
        380  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          6  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed
Now After adding Index, Oracle is able to provide the correct cardinality -CARD-9000 i.e 10 percent of the total number of records in the table. Even though i retrive only one tenth of records my index is not used, it is a full table scan.  This is different topic by itself,  lets get into cardinality issue in this blog. Though being a full table scan, posting adding an Index optimizer is able to provide the correct cardinality, Does this mean that the optimizer for full table scan uses the Index stats on avalibility ? If that is the case what happens if i drop the index ?         
SQL> drop index idx_fts_chk1;

Index dropped.

Elapsed: 00:00:00.46
SQL>
SQL> EXEC DBMS_STATS.gather_table_stats('HARI', 'TBL_FTS_CHK', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:03.15
SQL>
SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = :a1 AND T2.ID_VAL = :a2;

9000 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |   900 | 19800 |   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |   900 | 19800 |   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID_VAL"=:A2 AND "T2"."ID"=TO_NUMBER(:A1))                                                                                         


Statistics
----------------------------------------------------------                                                                                            
        169  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
        990  consistent gets                                                                                                                          
          0  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          5  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed
 
Oh, back to 900 (1 percent of records) in the cardinality, Thus it is very evident that Oracle Full table scan uses Index stats to provide a better cardinality estimate. Now the question here is should i go for Index to help the optimizer to provide the correct cardinality estimate, Can't i just provide more details about these two columns ID and ID_VAL to the optimizer so that it helps in estimating the correct cardinality ?

           How should i inform the optimizer about these two columns? And that's all about Extended Statistics in Oracle 11g. First lets create the additional stats on the required columns using DBMS_STATS 
SQL> 
SQL> SELECT dbms_stats.create_extended_stats(OWNNAME=>'HARI',TABNAME=>'TBL_FTS_CHK',EXTENSION=>'(ID,ID_VAL)') FROM DUAL;

DBMS_STATS.CREATE_EXTENDED_STATS(OWNNAME=>'HARI',TABNAME=>'TBL_FTS_CHK',EXTENSION=>'(ID,ID_VAL)')                                                     
------------------------------------------------------------------------------------------------------------------------------------------------------
SYS_STUSBSF29XATPMX0WFZ7WKDVIV                                                                                                                        

Elapsed: 00:00:00.21


Collecting stats on this table,  
SQL> EXEC DBMS_STATS.gather_table_stats('HARI', 'TBL_FTS_CHK', estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE);

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.95

          Note the dbms_stats is executed with default method_opt option - FOR ALL COLUMNS SIZE AUTO and auto sampling size. Now executing the query again
SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = :a1 AND T2.ID_VAL = :a2;

9000 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |   900 | 20700 |   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |   900 | 20700 |   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID_VAL"=:A2 AND "T2"."ID"=TO_NUMBER(:A1))                                                                                         


Statistics
----------------------------------------------------------                                                                                            
          8  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
        974  consistent gets                                                                                                                          
          0  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          0  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed                                                                                                                           

    oops still not the correct cardinality, lets try providing the literal instead of binds,


SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = 5 AND T2.ID_VAL = 'ORACLE5';

9000 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |   993 | 22839 |   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |   993 | 22839 |   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID"=5 AND "T2"."ID_VAL"='ORACLE5')                                                                                                


Statistics
----------------------------------------------------------                                                                                            
          8  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
        974  consistent gets                                                                                                                          
          0  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          0  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed                                                                              

Not yet, how about collecting stats with appropriate columns stats - method_opt => for all columns size 254

SQL> EXEC DBMS_STATS.gather_table_stats('HARI', 'TBL_FTS_CHK', estimate_percent => NULL,method_opt => 'for all columns size 254');

PL/SQL procedure successfully completed.

Elapsed: 00:00:01.27
SQL> 
SQL> 
SQL> 
SQL> 
SQL> 
SQL> exec :a1:=5;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> exec :a2:='ORACLE5';

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.00
SQL> 
SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = :a1 AND T2.ID_VAL = :a2;

9000 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |  9000 |   202K|   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |  9000 |   202K|   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID_VAL"=:A2 AND "T2"."ID"=TO_NUMBER(:A1))                                                                                         


Statistics
----------------------------------------------------------                                                                                            
          0  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
        972  consistent gets                                                                                                                          
          0  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          0  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed                                                                                                                           

SQL> SELECT * FROM TBL_FTS_CHK T2 WHERE T2.ID = 5 AND T2.ID_VAL = 'ORACLE5';

9000 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------                                                                                            
Plan hash value: 2042300665                                                                                                                           
                                                                                                                                                      
---------------------------------------------------------------------------------                                                                     
| Id  | Operation         | Name        | Rows  | Bytes | Cost (%CPU)| Time     |                                                                     
---------------------------------------------------------------------------------                                                                     
|   0 | SELECT STATEMENT  |             |  9000 |   202K|   103   (1)| 00:00:02 |                                                                     
|*  1 |  TABLE ACCESS FULL| TBL_FTS_CHK |  9000 |   202K|   103   (1)| 00:00:02 |                                                                     
---------------------------------------------------------------------------------                                                                     
                                                                                                                                                      
Predicate Information (identified by operation id):                                                                                                   
---------------------------------------------------                                                                                                   
                                                                                                                                                      
   1 - filter("T2"."ID"=5 AND "T2"."ID_VAL"='ORACLE5')                                                                                                


Statistics
----------------------------------------------------------                                                                                            
          0  recursive calls                                                                                                                          
          0  db block gets                                                                                                                            
        972  consistent gets                                                                                                                          
          0  physical reads                                                                                                                           
          0  redo size                                                                                                                                
     121681  bytes sent via SQL*Net to client                                                                                                         
       7008  bytes received via SQL*Net from client                                                                                                   
        601  SQL*Net roundtrips to/from client                                                                                                        
          0  sorts (memory)                                                                                                                           
          0  sorts (disk)                                                                                                                             
       9000  rows processed                                             


Finally the optimizer is now able to provide the correct cardinality and has extended stats for the correlation between these two columns ID and ID_VAL.And yes Oracle uses Index (stats) for Full table scan to provide better cardinality estimates.