IN ORACLE MILIEU …

Beyond Knowledge

Instance/service registration with Database listener

I have seen many times DBAs are getting confused with Static registration and dynamic registration of services/instance with listener.
As far back I remember, dynamic registration of services was introduced in Oracle 9i.

In this article, I am going to cover everything about static and dynamic service/instance registration with listener and what does it mean.

Many times you must have seen following error while making remote connection to database
ERROR:
ORA-12514: TNS:listener does not currently know of service requested in connect
Descriptor
Understanding of service/instance registration is essential to fix this issue.

Static Registration of instances/service – How it works

Static Instance Registration:

We all know that listener is a separate process that runs on database server and is responsible for receiving client connection and passing connection to database. After connection is handed over to database, listener is out of the picture.

Question is how does listener know what is the instance name/service name and where he should send the client connection ?
One way to do it is using static registration. This is done by including SID_LIST section in listener.ora configuration file.

Example, my listener.ora file looks like following

 

l_deo_001 =
    (description =
        (address_list =
            (address=(protocol=tcp)(host=deo-server.example.com)(port=1529))))
sid_list_l_deo_001 =
    (sid_list =
        (sid_desc =
            (sid_name = deo)
            (oracle_home = /opt/app/oracle/product/db_1))
    )

 

If you see I have a section called “SID_LIST_<listener_name>” which tells listener that SID name is “deo”.

If we check listener status we will see following

lsnrctl status l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 09:17:58

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 09:02:55
Uptime                    0 days 0 hr. 15 min. 2 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
Services Summary...
Service "deo" has 1 instance(s).
  Instance "deo", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

“Instance “deo”, status UNKNOWN” meaning that instance DEO is registered with listener l_deo_001.

Why status is unknown is because listener does not know whether the instance is really up and available or not. There is no mechanism to guarantee that the specified instance even exists. The listener just assumes that the instance will be there when a connect request is received.

My tnsping is as below

(DESCRIPTION = (ENABLE = BROKEN) (LOAD_BALANCE = YES) (ADDRESS = (PROTOCOL = TCP)(HOST = deo-server.example.com)(PORT = 1529)) (CONNECT_DATA = (SID = deo)))

So I have “SID = deo” in my connect string. And since this SID/instance is registered with my listener, my connections goes fine with database (Offcource using correct host and port is required).

Static Service Registration:

Same is the case with service. Example if I have following TNS alias which is using SERVICE_NAME in CONNECT_DATA

(DESCRIPTION = (ENABLE = BROKEN) (LOAD_BALANCE = YES) (ADDRESS = (PROTOCOL = TCP)(HOST = deo-server.example.com)(PORT = 1529)) (CONNECT_DATA = (SERVICE_NAME = adg_deo)))

Now if you want to connect to database using above TNS alias which has SERVICE_NAME, you need to have this service registered in listener port

Way to do that using static registration is to use GLOBAL_DBNAME = <service_name> in listener.ora parameter as shown below and bounce listener for this to take affect

l_deo_001 =
    (description =
        (address_list =
            (address=(protocol=tcp)(host=deo-server.example.com)(port=1529))))

sid_list_l_deo_001 =
    (sid_list =
        (sid_desc =
            (sid_name = deo)
            (oracle_home = /opt/app/oracle/product/db_1))
    (sid_desc =
            (GLOBAL_DBNAME = adg_deo.example)
            (sid_name = deo)
            (oracle_home = /opt/app/oracle/product/db_1))
    )

 

After that listener will show adg_deo service

lsnrctl start l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 10:24:10

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Starting /opt/app/oracle/product/db_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 11.2.0.3.0 - Production
System parameter file is /opt/app/oracle/product/db_1/network/admin/listener.ora
Log messages written to /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 10:24:10
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
Services Summary...
Service "adg_deo.example" has 1 instance(s).
  Instance "deo", status UNKNOWN, has 1 handler(s) for this service...
Service "deo" has 1 instance(s).
  Instance "deo", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

 

After that you will be able to connect using SERVICE_NAME = adg_deo.example

Remember that in static registration you need to have instance name in SID_LIST section in listener.ora as specified by (sid_name = deo). If you have some service_name configured in TNS alias, you need to make sure that in case of static registration those service_names should be part of listener.ora file as specified by (GLOBAL_DBNAME = adg_deo.example).

Now, if I remove SID_LIST section from listener.ora, listener still works. Here is the change

My listener.ora now looks as below

l_deo_001 =
    (description =
        (address_list =
            (address=(protocol=tcp)(host=deo-server.example.com)(port=1529))))

 

If I check listener status
lsnrctl start l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 09:01:25

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Starting /opt/app/oracle/product/db_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 11.2.0.3.0 - Production
System parameter file is /opt/app/oracle/product/db_1/network/admin/listener.ora
Log messages written to /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 09:01:25
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
The listener supports no services
The command completed successfully

It says “The listener supports no services”. If you try connecting now, you will hit following error

 

ERROR:
ORA-12514: TNS:listener does not currently know of service requested in connect
Descriptor

Why? because your instance is not registered with listener. So l_deo_001 is just a standalone listener process running on the host with no instances registered with it. It doesnt transfer connections to any database. So connecting to “deo” database using this listener will not work.

Dynamic Registration of instances/service – How it works

Dynamic Instance Registration:

Do we need to have SID_LIST always in listener.ora file ? The answer is NO. This requirement is gone since Oracle 9i when dynamic registration was introduced. In dynamic registration database automatically register the instances/services to listener ports.

Then how does instance gets registered with listener ?

To answer that, I have to explain the concept of local_listener parameter.

If you are using default port (1521) for listener, then database will automatically register the instance with listener. To test this out, lets start another listener which will have default name and port (1521)

lsnrctl start

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 09:57:44

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Starting /opt/app/oracle/product/db_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 11.2.0.3.0 - Production
System parameter file is /opt/app/oracle/product/db_1/network/admin/listener.ora
Log messages written to /opt/app/oracle/diag/tnslsnr/db-deo-r1/listener/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1521)))

Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 09:57:44
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/listener/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1521)))
The listener supports no services
The command completed successfully

 

It still says that “The listener supports no services”. To have database register the instance, you need to bounce the database so that during the start of DB, it will register the instance name with default listener

Now if you check the listener status, you will see service is registered automatically with default listener.

lsnrctl status

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 10:03:13

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))
STATUS of the LISTENER
------------------------
Alias                     LISTENER
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 09:57:44
Uptime                    0 days 0 hr. 5 min. 28 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/listener/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1521)))
Services Summary...
Service "deo_a.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
Service "deo_a_DGB.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
The command completed successfully

 

But what if you are not using default listener port (1521). Instead, you are using some other port – Say 1529. Oracle doesnt do dyamic registration on non-default listener port automatically.

So how do we tell Oracle what ports our listener is running on ? Answer to this question is local_listener parameter.

Lets stop default listener and start our original listener l_deo_001 on 1529 port. Also, I am not having SID_LIST section in my listener.ora file as I am going for dynamic registration

l_deo_001 =
    (description =
        (address_list =
            (address=(protocol=tcp)(host=deo-server.example.com)(port=1529))))
lsnrctl stop

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 10:08:42

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Connecting to (ADDRESS=(PROTOCOL=tcp)(HOST=)(PORT=1521))
The command completed successfully

lsnrctl start l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 10:10:49

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Starting /opt/app/oracle/product/db_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 11.2.0.3.0 - Production
System parameter file is /opt/app/oracle/product/db_1/network/admin/listener.ora
Log messages written to /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 10:10:49
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
The listener supports no services
The command completed successfully

In local_listener parameter we can specify non-default ports on which listener is running and Oracle will register services on those ports.

 

Example in my case, I am setting local_listener to following value

alter system set local_listener = '(address_list=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))';

As soon as I set the value of local_listener, PMON will know the port number on which listener is running and it will dynamically register the instance with listener.

lsnrctl status l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 02-APR-2013 10:12:35

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                02-APR-2013 10:10:49
Uptime                    0 days 0 hr. 1 min. 45 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
Services Summary...
Service "deo_a.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
Service "deo_a_DGB.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
The command completed successfully

 

So by setting LOCAL_LISTENER to the values appropriate for the listener, Oracle PMON process is able to contact the listener and register its instance.

 

Dynamic Service Registration:

We have seen dynamic instance registration which needs LOCAL_LISTENER database parameter to be set if we are not running the listener on default port (1521). What if we are using SERVICE_NAME in our TNS connect string as shown below

(DESCRIPTION = (ENABLE = BROKEN) (LOAD_BALANCE = YES) (ADDRESS = (PROTOCOL = TCP)(HOST = deo-server.example.com)(PORT = 1529)) (CONNECT_DATA = (SERVICE_NAME = adg_deo)))

If we want to register the service name dynamically in listener, we need to set service_names parameter in database

Currently we dont have ADG_DEO service in our listener

lsnrctl start l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 03-APR-2013 06:56:24

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Starting /opt/app/oracle/product/db_1/bin/tnslsnr: please wait...

TNSLSNR for Linux: Version 11.2.0.3.0 - Production
System parameter file is /opt/app/oracle/product/db_1/network/admin/listener.ora
Log messages written to /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening on: (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                03-APR-2013 06:56:24
Uptime                    0 days 0 hr. 0 min. 0 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
Services Summary...
Service "deo" has 1 instance(s).
  Instance "deo", status UNKNOWN, has 1 handler(s) for this service...
The command completed successfully

 

I have local_listener value set as below

 

SYS.DEO>show parameters local_listener

NAME_COL_PLUS_SHOW_PARAM                             TYPE
-------------------------------------------------------------------------------- -----------
VALUE_COL_PLUS_SHOW_PARAM
-------------------------------------------------------------------------------------
local_listener                                     string
(address_list=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))

Now I will set service_names parameter as below

SYS.DEO>alter system set service_names = 'adg_deo.example';

System altered.

SYS.DEO>

 

If you check the status of listener now, it will have that service name shown in the list

 

lsnrctl status l_deo_001

LSNRCTL for Linux: Version 11.2.0.3.0 - Production on 03-APR-2013 06:58:04

Copyright (c) 1991, 2011, Oracle.  All rights reserved.

Connecting to (DESCRIPTION=(address=(protocol=tcp)(host=deo-server.example.com)(port=1529)))
STATUS of the LISTENER
------------------------
Alias                     l_deo_001
Version                   TNSLSNR for Linux: Version 11.2.0.3.0 - Production
Start Date                03-APR-2013 06:56:24
Uptime                    0 days 0 hr. 1 min. 40 sec
Trace Level               off
Security                  ON: Local OS Authentication
SNMP                      OFF
Listener Parameter File   /opt/app/oracle/product/db_1/network/admin/listener.ora
Listener Log File         /opt/app/oracle/diag/tnslsnr/db-deo-r1/l_deo_001/alert/log.xml
Listening Endpoints Summary...
  (DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=deo-server.example.com)(PORT=1529)))
Services Summary...
Service "adg_deo.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
Service "deo" has 1 instance(s).
  Instance "deo", status UNKNOWN, has 1 handler(s) for this service...
Service "deo_a.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
Service "deo_a_DGB.example" has 1 instance(s).
  Instance "deo", status READY, has 1 handler(s) for this service...
The command completed successfully

So basically all the services which are listed in service_names database parameter will be registered by PMON in listener ports which are mentioned in local_listener parameter.

I hope I am able to clearify how static and dynamic instnace/service registration works.

 

Oracle SQL Patch – I

In my previous posts we have seen fixing plans by applying baselines and profiles.
For profiles we saw in details how to generate the same using SQL hints.

This article is about another feature of Oracle 11.2, called SQL Patch.
I am not sure if this is supported by Oracle, but in days to come they will make this official.
This is a kind of silver bullet for doing minor changes in the plan which is difficult to get it done using baselines and profiles.

What is SQL Patch:

A SQL patch is a SQL manageability object that can be generated by the SQL Repair Advisor, in order to circumvent a plan which causes a failure.  In essence, a SQL patch tells the optimizer to change the plan in some way or avoid an optimization so that the failure does not occur.  For instance, if a failure happens during index access, then generating a plan without that index (or without any index) may work around the failure.  You can read more about this in the documentation here

But we are going to use SQL Patch to fix a query plan.

Lets take an example:

SQL>create table T as select * from dba_objects;

Table created.

SQL>create index T_IDX on T(object_ID);

Index created.

SQL>exec dbms_stats.gather_table_stats(USER,'T',cascade=>true);

PL/SQL procedure successfully completed.

SQL>explain plan for 
  2  select * from T where object_id = 10;

Explained.

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation      | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |     |     1 |    89 |    62   (2)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T     |     1 |    89 |    62   (2)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter("OBJECT_ID"=10)

13 rows selected.

SQL>

We see that its going for a FTS for accessing the table.

Now, lets try to use patch so that same query will start using index.

We can use sys.dbms_sqldiag_internal.i_create_patch procedure to create patch
This procedure needs

- sql text
- hints to be applied
- catagory in which to save the patch
- name of the SQL Patch

If SQL text is too big, I have given a procedure at the end of this artical which can be used. It ask for SQL ID, child number and hint to be applied. Its very easy to use.

So lets try to create a SQL patch using sys.dbms_sqldiag_internal.i_create_patch procedure

 

SQL>exec sys.dbms_sqldiag_internal.i_create_patch(sql_text  => 'select * from T where object_id = 10',hint_text => 'INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."OBJECT_ID"))',category  => 'DEFAULT',name => 'PATCH_gz85dtvwaj4fw');

PL/SQL procedure successfully completed.

SQL>select count(1) from dba_sql_patches where name = 'PATCH_gz85dtvwaj4fw';

  COUNT(1)
----------
     1

1 row selected.

SQL>

 

Here are the meanings of values I provided

sql_text – This is the text of the SQL. If text is too long, conside using PLSQL procedure provided at the end of the article.

hint_text – This is the hint we want to provide. Now this will seem different that what we usually provide in the SQL. If you are not sure of the exact hint, here is what you can do.

I used normal hint in my SQL to generate a plan

explain plan for
select /*+ index(T_IDX T) */ * from T where object_id = 10;

based on this you can parse other_xml column in plan_table using following SQL

SELECT regexp_replace(extractvalue(value(d), '/hint'),'''','''''') plan_hint
        from
        xmltable('/*/outline_data/hint'
                passing (
                        select
                                xmltype(other_xml) as xmlval
                        from    plan_table
                        where   other_xml is not null
                        and     plan_id = (select max(plan_id) from plan_table)
                        and     rownum=1
                        )
                ) d;

PLAN_HINT
--------------------------------------------------------------------------------------------------------------------------------------------
INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."OBJECT_ID"))
OUTLINE_LEAF(@"SEL$1")
OPT_PARAM(''optimizer_index_caching'' 80)
OPT_PARAM(''optimizer_index_cost_adj'' 1)
OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')
OPT_PARAM(''_optim_peek_user_binds'' ''false'')
OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')
DB_VERSION(''11.2.0.2'')
OPTIMIZER_FEATURES_ENABLE(''11.2.0.2'')
IGNORE_OPTIM_EMBEDDED_HINTS

10 rows selected.

 

Once you get the output, you can pick the index hint from above – INDEX_RS_ASC(@”SEL$1″ “T”@”SEL$1″ (“T”.”OBJECT_ID”))

Catagory can be any catagory you want to have. Usually everything should go to default if not specified.

Name of the patch can also be anything you want to give.

Now, if we check the plan for original query, it will start using index.

 

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

PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation            | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |        |      1 |     89 |    200   (0)| 00:00:03 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |      1 |     89 |    200   (0)| 00:00:03 |
|*  2 |   INDEX RANGE SCAN        | T_IDX |      1 |        |    100   (0)| 00:00:02 |
-------------------------------------------------------------------------------------

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

   2 - access("OBJECT_ID"=10)

Note
-----
   - SQL patch "PATCH_gz85dtvwaj4fw" used for this statement
   - SQL plan baseline "SQL_PLAN_2anpx5hbuf3cbae82cf72" used for this statement

19 rows selected.

SQL>

 

Also, in the Note section you can see patch “PATCH_gz85dtvwaj4fw” is getting used for this statement.

Hope this helps !!

Redefining tables online – Oracle 11g

Introduction:

One of the many challenges that we face in production environment is make changes to big tables.
If you consider a case of any OLTP systems, its easy to have tables whose size is beyond 10G.

This again depends on the nature of the database and kind of transactions happening on the table.

So lets consider a case where you have a big table which is also a very hot table having very high number of transactions / sec and something that continues 24X7.
Its difficult to take downtime on such tables and making DDL changes to such tables could be a nightmare.

Oracle has a great feature introudcued since Oracle 9i, but many DBAs doesnt seem to be aware of this feature – Online table redefiniation.

Online table redefinition allows you to make DDL changes to the table definition and requires very little downtime (less than a minute).
Techinically its not the same table that gets modified, but its another copy of the same table which has the required modification made.

You might question if we are making a new copy of the table we can as well use CTAS (Create Table as Select) and make the required changes.
But its not just about creating new object and copying the data. Online redefinition does lot more than that.

I will briefly explain you the features of online redefinition, followed by process and then we will straight way get to the examples which will help you to understand better.

Features – What it can do:

Online table redefinition enables you to:

  • Modify the storage parameters of a table or cluster
  • Move a table or cluster to a different tablespace
  • Add, modify, or drop one or more columns in a table or cluster
  • Add or drop partitioning support (non-clustered tables only)
  • Change partition structure
  • Change physical properties of a single table partition, including moving it to a different tablespace in the same schema
  • Change physical properties of a materialized view log or an Oracle Streams Advanced Queueing queue table
  • Re-create a table or cluster to reduce fragmentation

Process – How its done:

To briefly explain the process, it involves following steps

1) Check if the table can be refined by running DBMS_REDEFINITION.CAN_REDEF_TABLE procedure.

We have following restrictions on redefining the table

  • One cannot redefine Materialized Views (MViews) and tables with MViews or MView Logs defined on them.
  • One cannot redefine Temporary and Clustered Tables
  • One cannot redefine tables with BFILE, LONG or LONG RAW columns
  • One cannot redefine tables belonging to SYS or SYSTEM
  • One cannot redefine Object tables
  • Table redefinition cannot be done in NOLOGGING mode (watch out for heavy archiving)

2) Create a new table with all of the desired logical and physical attributes.

If you want to change non-partition table to partition, you can create a new partition table. Structure of the table should be exactly the way you want to convert to.

3) Start redefinition process using DBMS_REDEFINITION.START_REDEF_TABLE procedure.

Be careful before running this command. If you must know, this command will start populating new table from the data in old table.
So if your old table is very big, then you need to have same amount of space available in the tablespace where new table is created.
Also, this command might take very long time if the size is big, so make sure you don’t have any disconnection in between.

If needed you can enable parallel before starting redefinition using following commands

alter session force parallel dml parallel degree-of-parallelism;
alter session force parallel query parallel degree-of-parallelism;

4) Sync new table on regular basis till cut off time

You should use DBMS_REDEFINITION.SYNC_INTERIM_TABLE and keep new table in sync with changes that happens on current production table.
This will reduce the cut off time. Cut off time if when you are going to point everything to new table and services will start writing to new table.
The more you keep new table and current production table in sync, lesser will be cut off time and downtime.

5) Copy dependent objects (such as triggers, indexes, grants, and constraints)

Copy dependent objects (such as triggers, indexes, materialized view logs, grants, and constraints) and statistics from production table to new table.
You should make sure that all dependents are copied.
You can do this manually by creating each dependent object or you can do it automatically using DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS

6) Complete redefinition process by using DBMS_REDEFINITION.FINISH_REDEF_TABLE procedure

This will complete the redefinition process. This needs exclusive lock on production table which you want to redefine.
So you need to arrange for short downtime. However, FINISH_REDEF_TABLE will wait for all pending DML to commit before completing the redefinition.

Example:

Lets take an example:

We have a table T as shown below.
We have a primary key on OBJECT_ID column.
We have a public synonym for table T.

SQL>desc T
 Name                       Null?    Type
 ----------------------------------------- -------- ----------------------------
 OWNER                            VARCHAR2(30)
 OBJECT_NAME                        VARCHAR2(128)
 SUBOBJECT_NAME                     VARCHAR2(30)
 OBJECT_ID                        NUMBER
 DATA_OBJECT_ID                     NUMBER
 OBJECT_TYPE                        VARCHAR2(19)
 CREATED                        DATE
 LAST_DDL_TIME                        DATE
 TIMESTAMP                        VARCHAR2(19)
 STATUS                         VARCHAR2(7)
 TEMPORARY                        VARCHAR2(1)
 GENERATED                        VARCHAR2(1)
 SECONDARY                        VARCHAR2(1)
 NAMESPACE                        NUMBER
 EDITION_NAME                        VARCHAR2(30)

SQL>

Currently this is not a partitioned table

SQL>select table_name, partitioned from user_tables where table_name = 'T';

TABLE_NAME               PAR
------------------------------ ---
T                   NO

Lets try to convert this into partition table.

You can check the meaning of every parameter supplied to below procedures at – http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_redefi.htm
Step 1) Check if the table can be refined by running DBMS_REDEFINITION.CAN_REDEF_TABLE procedure.

SQL>set serveroutput on
SQL>EXEC DBMS_REDEFINITION.CAN_REDEF_TABLE(user,'T',DBMS_REDEFINITION.CONS_USE_PK);

PL/SQL procedure successfully completed.

SQL>

If the table is not a candidate for online redefinition, an error message is raised.

Step 2) Create a new intrim table with all of the desired logical and physical attributes.

For table T lets try to partition by CREATED which is a date column. I am planning to partition by year so we can get 10 partitions

SQL>select to_char(CREATED,'YYYY') from T group by to_char(CREATED,'YYYY');

TO_C
----
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012

10 rows selected.

CREATE TABLE "T_INTRIM"
   (    "OWNER" VARCHAR2(30),
    "OBJECT_NAME" VARCHAR2(128),
    "SUBOBJECT_NAME" VARCHAR2(30),
    "OBJECT_ID" NUMBER,
    "DATA_OBJECT_ID" NUMBER,
    "OBJECT_TYPE" VARCHAR2(19),
    "CREATED" DATE,
    "LAST_DDL_TIME" DATE,
    "TIMESTAMP" VARCHAR2(19),
    "STATUS" VARCHAR2(7),
    "TEMPORARY" VARCHAR2(1),
    "GENERATED" VARCHAR2(1),
    "SECONDARY" VARCHAR2(1),
    "NAMESPACE" NUMBER,
    "EDITION_NAME" VARCHAR2(30),
     CONSTRAINT "T_PK_INTRIM_01" PRIMARY KEY ("OBJECT_ID")
    )
PARTITION BY RANGE(CREATED)
(
PARTITION T_2003 VALUES LESS THAN (TO_DATE('01-JAN-2004','DD-MON-YYYY')),
PARTITION T_2004 VALUES LESS THAN (TO_DATE('01-JAN-2005','DD-MON-YYYY')),
PARTITION T_2005 VALUES LESS THAN (TO_DATE('01-JAN-2006','DD-MON-YYYY')),
PARTITION T_2006 VALUES LESS THAN (TO_DATE('01-JAN-2007','DD-MON-YYYY')),
PARTITION T_2007 VALUES LESS THAN (TO_DATE('01-JAN-2008','DD-MON-YYYY')),
PARTITION T_2008 VALUES LESS THAN (TO_DATE('01-JAN-2009','DD-MON-YYYY')),
PARTITION T_2009 VALUES LESS THAN (TO_DATE('01-JAN-2010','DD-MON-YYYY')),
PARTITION T_2010 VALUES LESS THAN (TO_DATE('01-JAN-2011','DD-MON-YYYY')),
PARTITION T_2011 VALUES LESS THAN (TO_DATE('01-JAN-2012','DD-MON-YYYY')),
PARTITION T_2012 VALUES LESS THAN (TO_DATE('01-JAN-2013','DD-MON-YYYY'))
);

3) Start redefinition process using DBMS_REDEFINITION.START_REDEF_TABLE procedure.

BEGIN
DBMS_REDEFINITION.START_REDEF_TABLE(
   uname        => user,
   orig_table   => 'T',
   int_table    => 'T_INTRIM',
   col_mapping  => NULL,
   options_flag => DBMS_REDEFINITION.CONS_USE_ROWID
   );
END;
/

After this both table should have near about same amount of record

SQL>select count(1) from T_INTRIM;

  COUNT(1)
----------
     61536

SQL>select count(1) from T;

  COUNT(1)
----------
     61536

SQL>

If you have continuous inserts going on your original table than you might have little more records in original table than intrim table.

4) Sync new table on regular basis till cut off time

BEGIN 
DBMS_REDEFINITION.SYNC_INTERIM_TABLE(
   uname      => user, 
   orig_table => 'T', 
   int_table  => 'T_INTRIM'
   );
END;
/

The way this sync works is, online redefinition will automatically create a MLOG table on original table.
In any of the above step we didn’t create any MLOG table on table T.

But if you check now, you will see MLOG table created automatically.

SQL>select log_table from user_snapshot_logs where master = 'T';

LOG_TABLE
------------------------------
MLOG$_T

This is required for syncing changed made to table T.

5) Copy dependent objects (such as triggers, indexes, grants, and constraints)

DECLARE
num_errors PLS_INTEGER;
BEGIN
DBMS_REDEFINITION.COPY_TABLE_DEPENDENTS(
    uname               => user,
    orig_table          => 'T',
    int_table           => 'T_INTRIM',
    copy_indexes        => DBMS_REDEFINITION.CONS_ORIG_PARAMS,
    copy_triggers       => TRUE,
    copy_constraints    => TRUE,
    copy_privileges     => TRUE,
    ignore_errors       => TRUE,
    num_errors          => num_errors);
END;
/

PL/SQL procedure successfully completed.

Before we finish online redefinition you can check if table is partition and data is distributed to all partitions

SQL>select table_name, partitioned from user_tables where table_name in ('T','T_INTRIM');

TABLE_NAME               PAR
------------------------------ ---
T                   NO
T_INTRIM               YES

SQL>select to_char(created,'YYYY'), count(1) from T_INTRIM group by to_char(created,'YYYY');

TO_C   COUNT(1)
---- ----------
2003       7902
2005       1820
2009       2742
2010       6765
2008       2612
2007       1016
2011      10474
2004        756
2012      23474
2006       3975

10 rows selected.

Once we finish redefinition table T will become partition table and T_INTRIM will become non-partition table.
For this it needs exclusive lock.

6) Complete redefinition process by using DBMS_REDEFINITION.FINISH_REDEF_TABLE procedure

BEGIN
DBMS_REDEFINITION.FINISH_REDEF_TABLE(user, 'T', 'T_INTRIM');
END;
/

PL/SQL procedure successfully completed.

SQL>select table_name, partitioned from user_tables where table_name in ('T','T_INTRIM');

TABLE_NAME               PAR
------------------------------ ---
T                   YES
T_INTRIM               NO

SQL>

so now table T is partitioned table.

References:

http://docs.oracle.com/cd/B28359_01/server.111/b28310/tables007.htm

http://docs.oracle.com/cd/B28359_01/appdev.111/b28419/d_redefi.htm

Hope this helps !!

Materialized view Concepts – Discussion Series 3

We have seen Materialized view Concepts – Discussion Series 1 and Materialized view Concepts – Discussion Series 2.
This is the third article about Materialized views.

In this article we are going to discuss “How fast refresh works ?”

 

How fast refresh works ?

As we know in case of fast refresh only the changes that happened on master site (or master table) will be applied to MView on target site.
So the changes that happens on master table will be stored in MLOG table created on top of master table.
This is more efficient way than doing complete refresh.

As we know MLOG can be based on primary key or ROWID. ROWID MLOG is uncommon and is not used because if master table gets moved then ROWID will get changed and changes saved in MLOG will be invalid.
So to identify the changes on master table usually primary key based MLOG is created on top of master table.

Only 1 MLOG can be created on master table even if we have multiple sites refreshing from 1 master table.

Before we check on how fast refresh works, lets understand some of the components of fast refresh

MLOG$ table and its important columns

SNAPTIME$$             - This is a date columns and holds the date of 1st Mview refresh time. Example if we have 3 Mviews registered on 1 master table, than this column will hold oldest refresh date among the 3 Mviews
DMLTYPE$$              - This column tells you the type of DML ( U – Update, D – Delete, I – Insert )
OLD_NEW$$             - This column allow the fast-refresh mechanism to distinguish between rows inserted at the mview site and rows with modified primary key values.
CHANGE_VECTOR$$     - Used for subquery and LOB MViews

Apart from above standard columns in MLOG$ table, there will also be primary key columns. These columns are same as primary key columns of master table

AFTER ROW trigger on the master table

From Oracle8 and onward this trigger is kernelized and is no longer visible in the data dictionary. The same trigger can support both ROWID and primary key MViews and will populate the MLOG$_<table_name> with the proper values. It also populates the SNAPTIME$$ column (indicating the latest refresh time so far a particular row) and the DMLTYPE$$ column. The snaptime$$ column is populated based on the value of the snaptime column in snap$ table at the MView site and is not updated until the log is first used by a MView refresh.

Registration of Mview on master site

Oracle automatically tries to register a materialized view at its master site or master materialized view site when you create the materialized view, and unregisters when you drop it. The same applies to materialized view groups. Registration of fast refreshable materialized view logs in the master database (SYS.SLOG$) is needed to perform fast refreshes. This information is also used to maintain the materialized view log of the master table.

Fast refresh operation

Fast refresh operation consists of 3 phases

  1. Setup Phase
  2. Refresh Phase
  3. Wrap-up Phase

1) Setup Phase:

Setup has to check if the Mview being refreshed is ROWID based Mview or Primary key based Mview.
After that it has to verify if fast-refresh can be performed for this MView. An MView can perform a fast refresh only if it can use the MView log. This can be determined by checking entry in SYS.SLOG$ table on master site.


DB1>select MASTER, SNAPID, SNAPTIME from SYS.SLOG$ where MASTER = 'T';

MASTER                   SNAPID SNAPTIME
------------------------------ ---------- ----------------
T                    15119 2012-10-14:03:01
T                    15120 2012-10-17:09:47

 

If an entry is present in this table for that SNAPID, then it can be fast refreshed.
Its possible that you have created a complete refreshable MView on some master table which didn’t had MLOG. Later point of time you created MLOG and may be wondering why fast refresh not happening.
You can check the entry in this table and verity. Also in such cases you need to drop and recreate Mview on prebuilt table and make it fast refreshable.

Once its confirmed that fast refresh is possible for MView in question, snaptime$$ column is updated in the MLOG$ table of the altered rows to its own refresh date and time for the first MView that refreshes. This value does not change until the rows are eventually purged from the log.

2) Refresh Operation:

After setup phase, a second check is made to see if fast refresh can be done. This time its the date comparison that is done to ensure their is no mismatch of data.
For fast refresh to work, timestamp of oldest/oldest_pk column in mlog$ must be older than the time of last refresh.

How fast refresh work

Lets check out in detail how the date changes happen.
We will keep an eye on 3 tables

1) SYS.SLOG$ on master
2) SYS.MLOG$ on master
3) MLOG$_<master_table> on master (This is out MLOG$ table)

I will explain you with live example.

Master table name – T (on DB1 database)
MLOG table name – MLOG$_T (no DB1 database)

We have 2 snapshot sites and each has fast refreshable MView created.

MView Name – T_REP (on DB2 database)
MView Name – T_REP (on DB3 database)

On Master site:


DB1>select MASTER, SNAPSHOT, SNAPID, SNAPTIME from SYS.SLOG$ where MASTER = 'T';

MASTER                   SNAPSHOT         SNAPID SNAPTIME
------------------------------ ---------------- ---------- ----------------
T                             15119 2012-10-24:01:16
T                             15120 2012-10-24:01:17

Above output represents 2 snapshot site registered on Master table/site

DB1>select MASTER, OLDEST_PK, YOUNGEST, LOG, MTIME, LAST_PURGE_DATE from SYS.MLOG$ where MASTER = 'T';

MASTER                   OLDEST_PK    YOUNGEST     LOG                MTIME         LAST_PURGE_DATE
------------------------------ ---------------- ---------------- ------------------------------ ---------------- ----------------
T                   2012-10-24:01:16 2012-10-24:01:17 MLOG$_T            2012-10-13:22:25 2012-10-24:01:17

 

In SYS.MLOG$ table you will have 1 record for each MLOG$ that you create. Since a master table can have only 1 MLOG you will have 1 record for each master table.

There are no records in MLOG$_T table

DB1>select count(1) from MLOG$_T;

  COUNT(1)
----------
     0

Lets make changes in master table T and see what happens in above 3 tables

DB1>update T set DATA_OBJECT_ID = 0 where OBJECT_ID = 2300;

1 row updated.

DB1>commit;

Commit complete.

DB1>

With above modification, no change seen in SYS.SLOG$ table and SYS.MLOG$ table
DB1>select MASTER, SNAPSHOT, SNAPID, SNAPTIME from SYS.SLOG$ where MASTER = 'T';

MASTER                   SNAPSHOT         SNAPID SNAPTIME
------------------------------ ---------------- ---------- ----------------
T                             15119 2012-10-24:01:16
T                             15120 2012-10-24:01:17

DB1>
DB1>select MASTER, OLDEST_PK, YOUNGEST, LOG, MTIME, LAST_PURGE_DATE from SYS.MLOG$ where MASTER = 'T';

MASTER                   OLDEST_PK    YOUNGEST     LOG                MTIME         LAST_PURGE_DATE
------------------------------ ---------------- ---------------- ------------------------------ ---------------- ----------------
T                   2012-10-24:01:16 2012-10-24:01:17 MLOG$_T            2012-10-13:22:25 2012-10-24:01:17

However we see new entry in MLOG$_T table corresponding to the row changed

DB1>select OBJECT_ID, SNAPTIME$$, DMLTYPE$$, OLD_NEW$$ from MLOG$_T;

 OBJECT_ID SNAPTIME$$        D O
---------- ---------------- - -
      2300 4000-01-01:00:00 U U

OBJECT_ID is the primary key column and value in MLOG$_T table represent the row we changed

Observe the value of SNAPTIME$$ – 4000-01-01. This is a date in future which will not be reached in lifetimes. This date tells us that none of the Mview sites has done a fast refresh of this change.

So lets rewind our statement about second check done during “Refresh Phase”

For fast refresh to work, timestamp of oldest/oldest_pk column in mlog$ must be older than the time of last refresh.

It means oldest_pk <= last refresh time on Mview sites

We can check last refresh time of Mviews by checking DBA_SHAPSHOTS table or DBA_SNAPSHOT_REFRESH_TIMES table on Mview site.  But same thing can be checked on master site using SYS.SLOG$ table

We know oldest_pk timestamp – 2012-10-24:01:16
Min(last_refresh time) – 2012-10-24:01:16

So since oldest_pk <= last refresh time on Mview sites, refresh can proceed

If one of the site does a fast refresh we can see that MLOG$_T.SNAPTIME$$ timestamp gets updated to refresh time

DB2>exec dbms_snapshot.refresh('T_REP','F');

PL/SQL procedure successfully completed.

DB2>

DB1>select OBJECT_ID, SNAPTIME$$, DMLTYPE$$, OLD_NEW$$ from MLOG$_T;

 OBJECT_ID SNAPTIME$$        D O
---------- ---------------- - -
      2300 2012-10-24:01:52 U U

DB1>

We are yet to refresh second site which is registered for this master table and so the record from MLOG$_T is not deleted.

Also, 1 note with respect to SYS.MLOG$ table


DB1>select MASTER, OLDEST_PK, YOUNGEST, LOG, MTIME, LAST_PURGE_DATE from SYS.MLOG$ where MASTER = 'T';

MASTER                   OLDEST_PK    YOUNGEST     LOG                MTIME         LAST_PURGE_DATE
------------------------------ ---------------- ---------------- ------------------------------ ---------------- ----------------
T                   2012-10-24:01:17 2012-10-24:01:52 MLOG$_T            2012-10-13:22:25 2012-10-24:01:52

 

YOUNGEST column represent the latest refresh time. If you have 5 sites, the one you refresh latest will have that timestamp updated in YOUNGEST column
OLDEST_PK column represent the oldest refresh time. If you have 5 sites, the one you refresh first will have that timestamp updated in OLDEST_PK column
OLDEST column is used in ROWID based MLOG

3) Wrap-up Phase

In this phase it checks if all Mviews are refreshed and if the changes in MLOG$_T table has gone to all site and if the entries in MLOG$_T table can be purged.

Again Oracle checks dates in above 3 tables to determine which records in MLOG$ table can be purged.

How MLOG$ purge works

Oracle automatically tracks which rows in a MView log have been used during the refreshes of MViews, and purges these rows from the log so that the log does not grow endlessly. Because multiple simple mviews can use the same MView log, rows already used to refresh one MView may still be needed to refresh another MView. Oracle does not delete rows from the log until all MViews have used them. In the wrap-up phase of the refresh process, the master MView log is purged. It deletes all unnecessary rows from the MView log. Rows in the MView log are unnecessary if their refresh timestamps MLOG$_<table_name>.SNAPTIME$$ are older or equal than the oldest entry in SLOG$.SNAPTIME for this log.

It means rows in MLOG$_<master_table> will be deleted whose MLOG$_<master_table>.SNAPTIME$$ <= min(SYS.SLOG$.SNAPTIME)

Lets take an example

We have a live example going where we have updated 1 record in master table and we have refreshed 1 site (out of 2 sites registered)

Here are the outputs of 2 required tables


DB1>select OBJECT_ID, SNAPTIME$$, DMLTYPE$$, OLD_NEW$$ from MLOG$_T;

OBJECT_ID SNAPTIME$$        D O
---------- ---------------- - -
2300 2012-10-24:01:52 U U

DB1>select MASTER, SNAPSHOT, SNAPID, SNAPTIME from SYS.SLOG$ where MASTER = 'T';

MASTER                   SNAPSHOT         SNAPID SNAPTIME
------------------------------ ---------------- ---------- ----------------
T                             15119 2012-10-24:01:52
T                             15120 2012-10-24:01:17

As you can see SNAPTIME$$ timestamp has the time when this record was first refresh by any site. If we have 3 sites and 1st site refreshes the records this timestamp will get updated.
But if 2nd site refreshes the record, this timestamp will NOT change. However refresh of second site will change the SNAPTIME column in SYS.SLOG$ table. SNAPTIME column in SYS.SLOG$ table always has the latest refresh time for corresponding site.

So we can see that MLOG$_T.SNAPTIME$$ – 2012-10-24:01:52 (this is the time when 1st site got refreshed)
min(SYS.SLOG$.SNAPTIME) – 2012-10-24:01:17

Since MLOG$_T.SNAPTIME$$ > min(SYS.SLOG$.SNAPTIME), row will not be deleted.

If I refresh 2nd site than SYS.SLOG$.SNAPTIME corresponding to that site will get updated with refresh time and in that case MLOG$_T.SNAPTIME$$ <= MIN(SYS.SLOG$.SNAPTIME). When this condition happens it will delete the record from MLOG$ table.

Logically this means that all the sites registered for this master table is been refreshed.

Lets try to refresh 2nd site. This should purge the record from MLOG. We will also see how the dates are updated in 3 tables on master site

After refreshing second site

No rows in MLOG$_T table

DB1>select OBJECT_ID, SNAPTIME$$, DMLTYPE$$, OLD_NEW$$ from MLOG$_T;

no rows selected

SNAPTIME for second site got updated in SYS.SLOG$ table

DB1>select MASTER, SNAPSHOT, SNAPID, SNAPTIME from SYS.SLOG$ where MASTER = 'T';

MASTER                   SNAPSHOT         SNAPID SNAPTIME
------------------------------ ---------------- ---------- ----------------
T                             15119 2012-10-24:01:52
T                             15120 2012-10-24:02:56

In SYS.MLOG$ table, Previous YOUNGEST became OLDEST_PK and new YOUNGEST is the latest refreshed MView timestamp.
Also, LAST_PURGE_DATE gets updated when a record gets purged in MLOG$_T table


DB1>select MASTER, OLDEST_PK, YOUNGEST, LOG, MTIME, LAST_PURGE_DATE from SYS.MLOG$ where MASTER = 'T';

MASTER                   OLDEST_PK    YOUNGEST     LOG                MTIME         LAST_PURGE_DATE
------------------------------ ---------------- ---------------- ------------------------------ ---------------- ----------------
T                   2012-10-24:01:52 2012-10-24:02:56 MLOG$_T            2012-10-13:22:25 2012-10-24:02:56

In next article (probably last one of this discussion series), we will understand problems associated with MView Log, Modifying primary key on master table and what is “I am refresh”.

Hope this helps !!

Materialized Views Concepts – Discussion Series 2

We have seen Discussion Series 1 of materialized view concepts and we know how to create materialized view and also what each clause of Mview creation mean.

In this article we will see all backend tables that can be accessed to check the details of materialized view.

We will begin with identifying materialized view

DBA_SNAPSHOTS (On MView site)

Most important table for checking MView info is DBA_SNAPSHOTS.

We need to query this table on snapshot site (where MView is created)

 

SQL>select name, table_name, MASTER, MASTER_LINK, REFRESH_METHOD, UPDATABLE , LAST_REFRESH, STATUS, PREBUILT, REFRESH_MODE from dba_snapshots where name = 'T_REP';
NAME TABLE_NAME MASTER MASTER_LINK REFRESH_MET UPD LAST_REFRESH STATUS PRE REFRESH_
---------- ---------- -------------------- ------------------------------ ----------- --- ---------------- ------- --- --------
T_REP T_REP T @"DB1.AMAZON" PRIMARY KEY NO 2012-10-13:22:26 UNKNOWN YES DEMAND

 

DBA_REFRESH_CHILDREN (On Mview Site)

we can group multiple materialized views into a group and refresh all materialized view all at once.

This can be done by creating a refresh group.

You can see all steps to create refresh group at http://docs.oracle.com/cd/B28359_01/server.111/b28327/rarmviewgroup.htm

Once you create refresh group and add MView to it, you can see the info in MVIEW_REFRESH_GROUPS table

BEGIN
   DBMS_REFRESH.MAKE (
      name => 'TEST_REF_GROP',
      list => '', 
      next_date => SYSDATE, 
      interval => 'SYSDATE + 1/24');
END;
/

BEGIN
   DBMS_REFRESH.ADD (
      name => 'TEST_REF_GROP',
      list => 'T_REP',
lax => FALSE );
END;
/
SQL>select name, RNAME, REFGROUP, INTERVAL, TYPE from dba_refresh_children where name = 'T_REP';

NAME       RNAME REFGROUP INTERVAL TYPE
------------------------------ ------------------------------ ---------- ------------------------------ ------------------------------
T_REP       TEST_REF_GROP     3693 SYSDATE + 1/24 SNAPSHOT

SQL>

 

DBA_SNAPSHOT_LOGS (On Master site):

If we want to check information about MLOG table, we can view this table.

 

SQL>select master, LOG_TABLE, ROWIDS, PRIMARY_KEY, SNAPSHOT_ID, CURRENT_SNAPSHOTS from dba_snapshot_logs where master = 'T';
MASTER       LOG_TABLE       ROW PRI SNAPSHOT_ID CURRENT_SNAPSHOT
------------------------------ ------------------------------ --- --- ----------- ----------------
T       MLOG$_T       NO  YES     15119 2012-10-13:22:26

 

Since this is primary key based MLOG, we can see YES for primary key column. CURRENT_SNAPSHOT gives when was this last refreshed. This is same as LAST_REFRESH column in DBA_SNAPSHOTS

DBA_REGISTERED_SNAPSHOTS (On Master site):

To Check how many sites are registered for 1 master table, we can query DBA_REGISTERED_SNAPSHOTS

This table has a column called name which is basically the name of Mview on MVIEW site. Since each MVIEW site can have a different name we cannot compare this column to get list of sites registered for 1 master table.

But we don’t have any master column in this table so we join this table with DBA_SNAPSHOT_LOGS to get list of sites which are registered for a master table

 

SQL>select a.master, b.name, b.snapshot_site from dba_snapshot_logs a, dba_registered_snapshots b
  2  where a.snapshot_id = b.snapshot_id
  3  and a.master = 'T';

MASTER       NAME       SNAPSHOT_SITE
------------------------------ ------------------------------ ------------------------------
T       T_REP       DB1.AMAZON

 

To check which snapshots has delay

We can use following query to check which snapshots has refresh delay in mins

 

select a.master, b.name, b.snapshot_site, (sysdate - a.CURRENT_SNAPSHOTS)*24*60 "delay Mins"
from dba_snapshot_logs a, dba_registered_snapshots b
where a.snapshot_id = b.snapshot_id
and (sysdate - a.CURRENT_SNAPSHOTS)*24*60 > &delay;

 

This will ask for delay and you can enter delay in mins.

After that this will list down all snapshots which are having delay more than what you entered.

 

Example, if you want to list down all snapshots having delay of more than 10 mins, you need to enter 10.

x$knstmvr

This is another internal table which can be used to check the progress of snapshots.

Columns in this table is self understood.

 

Following query will provide you the details of snapshot progress

 

column mvowner format a10 
Column mvname format a30 
column refmode format a8 
column refstate format a12 
column inserts format 99999999 
column updates format 999999999 
column deletes format 999999999 
column event format a30 
column spid format a6 
select  currmvowner_knstmvr mvowner, 
currmvname_knstmvr mvname, 
decode( reftype_knstmvr, 0, 'FAST', 1, 'FAST', 2, 'COMPLETE', REFTYPE_KNSTMVR ) refmode, 
decode(groupstate_knstmvr, 1, 'SETUP', 2, 'INSTANTIATE',3, 'WRAPUP', 'UNKNOWN' ) refstate, 
total_inserts_knstmvr inserts, 
total_updates_knstmvr updates, 
total_deletes_knstmvr deletes, 
b.spid,c.event 
from x$knstmvr X, v$session a, v$process b, v$session_wait c 
WHERE type_knst=6 
and a.paddr = b.addr 
and a.sid = c.sid 
and x.SID_KNST = a.sid 
and x.SERIAL_KNST = a.serial#;

 

In the next article about MView discussion series we will discuss about MLOG table and some internal details about fast refresh and how it works.

Hope this helps !!

 

Materialized Views Concepts – Discussion Series 1

Materialized view concept: Why do we need materialized view?

Materialized views are nothing but views created on the base table and having data which is extracted from the base table.
How is materialized view different from the normal view.

Difference # 1:

Normal view does not contain data. It is just a transparent layer on the top of base

Materialized view contains data and additional space is required to create materialized view.
Difference # 2:

To use normal view, a user needs to provide the view name in the query

To use materialized view user does not need to provide materialized view name (although a user can, but its not required.)

Materialized views are required mainly for 2 reasons

1) Advanced replication of data from one location (database) to another location (database)

2) Summarizing the data in the table

When we are replicating the table from remote location to local location our queries can access the same data from local location which can lead to improved query performance.
Replication of data is possible using materialized views.

 

You can use materialized views to increase the speed of queries on very large databases. Queries to large databases often involve joins between tables, aggregations such as SUM, or both. These operations are expensive in terms of time and processing power. The type of materialized view you create determines how the materialized view is refreshed and used by query rewrite.

 

Materialized views improve query performance by precalculating expensive join and aggregation operations on the database prior to execution and storing the results in the database. The query optimizer automatically recognizes when an existing materialized view can and should be used to satisfy a request. It then transparently rewrites the request to use the materialized view. Queries go directly to the materialized view and not to the underlying detail tables. In general, rewriting queries to use materialized views rather than detail tables improves response time.

Based on the above definitions we have 3 situations where materialized views can be used:

1) Materialized Views for Data Warehouses

In data warehouses, you can use materialized views to precompute and store aggregated data such as the sum of sales. Materialized views in these environments are often referred to as summaries, because they store summarized data. They can also be used to precompute joins with or without aggregations. A materialized view eliminates the overhead associated with expensive joins and aggregations for a large or important class of queries.

2) Materialized Views for Distributed Computing

In distributed environments, you can use materialized views to replicate data at distributed sites and to synchronize updates done at those sites with conflict resolution methods. These replica materialized views provide local access to data that otherwise would have to be accessed from remote sites. Materialized views are also useful in remote data marts.

3) Materialized Views for Mobile Computing

You can also use materialized views to download a subset of data from central servers to mobile clients, with periodic refreshes and updates between clients and the central servers.

Its difficult to understand everything before we create a materialized view. So lets create a MView and understand every clause that can be given in creating MVIew.

Creating Materialized View:

Here goes the syntax of creating materialized view

CREATE MATERIALIZED VIEW SCHEMA.NAME 
(COL1, COL2, COL3 … )
ON PREBUILT TABLE / BUILD IMMEDIATE / BUILD DEFERRED
WITH / WITHOUT REDUCED PRECISION
USING INDEX / USING NO INDEX
TABLESPACE <TS NAME>
FOR UPDATE
REFRESH FAST / COMPLETE /  FORCE / ON DEMAND / ON COMMIT / START WITH .. NEXT / 
REFRESH WITH PRIMARY KEY / ROWID
ENABLE / DISABLE QUERY REWRITE
AS <SELECT QUERY ON MASTER TABLE>

This syntax is no way complete and there are many more clause related to storage, constraints, physical properties etc. But this is a basic DDL statement that is used most of the time to create materialized view.
If you want to see complete details of creating MView with all clauses, check reference section to get documentation link.

Lets discuss about each clause and what these values means to us.

ON PREBUILT TABLE

When you create a materialized view you can create the same on pre-built table. For example, before creating MView you were managing data replication using software and you were keeping a local table updated with latest data from some other master site. Now when you are implementing MView its a good option to use same table as prebuilt table and create MView on top of that. This way you can stop software replication and start using Mview.

If you don’t have any prebuilt table you can skip that clause along with “WITH REDUCED PRECISION” clause. If you are not using “ON PREBUILT TABLE” clause than you can use with “BUILD IMMEDIATE” or “BUILD DEFERRED” clause.
BUILD IMMEDIATE will immediately fetch the data info Mview. BUILD DEFERRED clause will defer fetching of data unless you do complete refresh manually.

Following things to be considered in case of PREBUILT Table

  • Materialized view name should be same as prebuilt table name. Master table can have different name
  • PREBUILT table can have extra column or less columns compared to master table, but Mview definition should include only those columns which are present in both master table and prebuilt table and corresponding columns must have matching datatypes.
  • If you don’t have any prebuilt table, create materialized view statement will create a table but it won’t be a prebuilt table. Meaning that if you drop Mview, your table will also be dropped (unless you use “preserve table” clause). But if its a prebuilt table, table will not be dropped even if you drop MView. This way you can retain data. So its essential to have a table marked as prebuilt table.

If you created materialized view without prebuilt table, it will create a table and there is a way to change that table as prebuilt table. So that if you drop materialized view some time in future, table will stay. Following update statement will convert a table into prebuilt table

update sys.snap$ set flag = 2228321 where vname = '<Mview Name>';
commit;

Above statement will convert a table into prebuilt table.

After that if you run the statement “drop materialized view <MView Name>;” it won’t drop prebuilt table.

Alternatively if you don’t have prebuilt table and you want to drop snapshot preserving the table you can use following statement

drop materialized view <MView Name> preserve table;

WITH / WITHOUT REDUCED PRECISION

Specify WITH REDUCED PRECISION to authorize the loss of precision that will result if the precision of columns in materialized view does not match with precision of column result returned by subquery. Remember that subquery is build on table on master site.
So this clause makes sense if you are using different precision of columns on Mview site than on master site.
WITHOUT REDUCED PRECISION will require the precision of columns in Mview to be exactly same as that of columns in table on master site (precision retuned by subquery).

USING INDEX

Using index clause can be used to specify the storage clause for index that will be created on materialized view. You can also specify the tablespace that should be used for all indexes on materialized view.
If you are creating ROWID based materialized view then “USING INDEX” clause will create default index like I_SNAP$_<table_name> which will be useful in internal MView management.
Using “NO INDEX VLAUSE” will prevent creating this default index and also you cannot specify storage clause and tablespace clause.

TABLESPACE <TS NAME>

This specifies the tablespace in which to create MVIEW.

ENABLE / DISABLE QUERY REWRITE

This clause will specify whether to enable or disable query rewrite on this MView.
One of the advantages of having materialized view is that our queries will automatically start using it (just like index)without doing any changes to the SQL. This capability is provided by query rewrite functionality.
The optimizer uses two different methods to recognize when to rewrite a query in terms of a materialized view. The first method is based on matching the SQL text of the query with the SQL text of the materialized view definition. If the first method fails, the optimizer uses the more general method in which it compares joins, selections, data columns, grouping columns, and aggregate functions between the query and materialized views.

You can find more details about query rewrite feature in link http://docs.oracle.com/cd/B19306_01/server.102/b14223/qrbasic.htm

REFRESH FAST / COMPLETE /  FORCE / ON DEMAND / ON COMMIT / START WITH .. NEXT /

Fast refresh can be based on primary key or it could be based on ROWID.
If fast refresh if based on primary key then, master table should have primary key defined. MView log will have primary key included. For fast refresh to work, you must have MLOG created on master table on master site using following commands

create materialized view log on <Master table name>;

Fast refresh is most efficient way of maintaining data as it brings only the changed data from master site to local MView. It maintains the changes in MLOG.
Not all Mviews can be fast refreshed. You can check the restrictions on creating fast refreshable materialized view at http://docs.oracle.com/cd/B28359_01/server.111/b28313/basicmv.htm#i1007028

Complete refresh does not need any MLOG. When we do complete refresh, complete data of master table is brought into MView. This method can be used for small tables.

Refresh Force will first try to refresh the Mview using fast method and if it doesn’t work, it will go for complete refresh.

Refresh On Demand will need DBAs to schedule a job which will do the refresh of Mview on periodic basis. If you don’t want to setup a job, you have to decide the schedule and refresh Mview yourself.

Refresh On commit will refresh Mview if a transaction gets committed on master site. We have to be careful with this option, if master site is located far away and commit rate is high, this is not a good option. Everytime a commit happens, it will try to refresh Mview, which is going to take time and eventually your transactions on master site will slow down.

Refresh Start with .. Next is used when we want to define the schedule in Mview definition while creating MView itself. This way Mview gets refreshed as per defined schedule

I tried to give basic overview of Materialized view and creating materialized view. Below are some examples of creating materialized view using above clause

 

Examples of Mview Creation

Example 1:

create materialized view T_REP
 ( OWNER,
   SUBOBJECT_NAME ,
   OBJECT_ID,
   DATA_OBJECT_ID ,
   OBJECT_TYPE,
   CREATED,
   LAST_DDL_TIME,
   TIMESTAMP,
   STATUS ,
   TEMPORARY,
   GENERATED,
   SECONDARY,
   NAMESPACE,
   EDITION_NAME)
 BUILD IMMEDIATE
 REFRESH ON DEMAND
 as 
 select OWNER,
        SUBOBJECT_NAME ,
        OBJECT_ID,
        DATA_OBJECT_ID ,
        OBJECT_TYPE,
        CREATED,
        LAST_DDL_TIME,
        TIMESTAMP,
        STATUS ,
        TEMPORARY,
        GENERATED,
        SECONDARY,
        NAMESPACE,
        EDITION_NAME
 from t@DB1;

Example 2:

Assuming we have primary key on master table and materialized view log on master site

create materialized view T_REP
 ( OWNER,
   SUBOBJECT_NAME ,
   OBJECT_ID,
   DATA_OBJECT_ID ,
   OBJECT_TYPE,
   CREATED,
   LAST_DDL_TIME,
   TIMESTAMP,
   STATUS ,
   TEMPORARY,
   GENERATED,
   SECONDARY,
   NAMESPACE,
   EDITION_NAME)
 ON PREBUILT TABLE
 WITH REDUCED PRECISION
 USING INDEX TABLESPACE OPS_IDX
 REFRESH FAST ON DEMAND
 ENABLE QUERY REWRITE
 as 
 select OWNER,
        SUBOBJECT_NAME ,
        OBJECT_ID,
        DATA_OBJECT_ID ,
        OBJECT_TYPE,
        CREATED,
        LAST_DDL_TIME,
        TIMESTAMP,
        STATUS ,
        TEMPORARY,
        GENERATED,
        SECONDARY,
        NAMESPACE,
        EDITION_NAME
 from t@DB1;

I hope this clears few things. This article is no way a replacement of Oracle documentation. I just tried to provide a concise information on creating materialized views.
My reference has links to Oracle Docs which provides detailed information.

In my next article of materialized view, I am going to provide some backend DBA tables which can be used to view information about materialized views.

Hope this helps !!

References:

Basic Mview creationhttp://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_6002.htm
Basics of Query rewritehttp://docs.oracle.com/cd/B19306_01/server.102/b14223/qrbasic.htm
Oracle By Example. Creating Mviewhttp://st-curriculum.oracle.com/obe/db/10g/r2/prod/bidw/mv/mv_otn.htm

Fixing SQL Plans: The hard way – Part 3

In part 1 of this series http://avdeo.com/2012/06/20/fixing-sql-plans-the-hard-way-part-1/ I showed you how to fix a SQL plan by picking good plan hints from other database and applying those hints/plan to your existing bad query

In part 2 of this series http://avdeo.com/2012/07/04/fixing-sql-plans-the-hard-way-part-2/ I showed you how to fix a SQL plan even if you don’t have another database having better plan. I explained you about hint you can use in the query and execute the query to get better plan. Then we can use the new plan generated and apply to the original SQL.

In this article we can go 1 step further. What if you don’t want to execute the SQL. May be because SQL gives too much of output or takes lot of time to complete.

We can generate the profile hints without even executing the SQL. This is possible using plan table

I am just explaining the steps which will be changed in this case

Once you identify the hint to be used, put the hint in the query and take explain plan

SQL>explain plan for
  2  select /*+ index(T T_IDX) */ count(1) from T where data_object_id > 65000 and status = 'VALID';

Explained.

SQL>@display

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1789076273

--------------------------------------------------------------------------------------
| Id  | Operation             | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |         |       1 |      12 |     953K  (1)| 00:38:35 |
|   1 |  SORT AGGREGATE          |         |       1 |      12 |          |         |
|*  2 |   TABLE ACCESS BY INDEX ROWID| T     | 16441 |     192K|     953K  (1)| 00:38:35 |
|*  3 |    INDEX RANGE SCAN         | T_IDX | 32883 |         |    8105   (3)| 00:00:20 |
--------------------------------------------------------------------------------------

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

   2 - filter("STATUS"='VALID')
   3 - access("DATA_OBJECT_ID">65000)

16 rows selected.

SQL>

Now if you check the plan table, you will have a column in plan_table called OTHER_XML

SQL>set long 9999
SQL>select other_xml from plan_table ;

OTHER_XML
--------------------------------------------------------------------------------

<other_xml><info type="db_version">11.2.0.2</info><info type="parse_schema"><![C
DATA["ORACLE_DBA"]]></info><info type="plan_hash">1789076273</info><info type="
plan_hash_2">388377992</info><outline_data><hint><![CDATA[INDEX_RS_ASC(@"SEL$1"
"T"@"SEL$1" ("T"."DATA_OBJECT_ID"))]]></hint><hint><![CDATA[OUTLINE_LEAF(@"SEL$1
")]]></hint><hint><![CDATA[OPT_PARAM('optimizer_index_caching' 80)]]></hint><hin
t><![CDATA[OPT_PARAM('optimizer_index_cost_adj' 9999)]]></hint><hint><![CDATA[OP
T_PARAM('_optimizer_connect_by_cost_based' 'false')]]></hint><hint><![CDATA[OPT_
PARAM('_optim_peek_user_binds' 'false')]]></hint><hint><![CDATA[OPT_PARAM('_b_tr
ee_bitmap_plans' 'false')]]></hint><hint><![CDATA[DB_VERSION('11.2.0.2')]]></hin
t><hint><![CDATA[OPTIMIZER_FEATURES_ENABLE('11.2.0.2')]]></hint><hint><![CDATA[I
GNORE_OPTIM_EMBEDDED_HINTS]]></hint></outline_data></other_xml>

SQL>

In the above query of plan table, I am assuming there will be only 1 plan.

Now we got the required plan in the form of XML which we used to get from V$SQL_PLAN.OTHER_XML

All we have to do now if follow same steps to parse this PLAN_TABLE.OTHER_XML column, get the hints and apply to original SQL.

Following are the steps

Get the required hints. Please note that I have used PLAN_TABLE now instead of V$SQL_PLAN. Also no need to give any SQL ID here as its going to get the hints of the query for which we did “explain plan for”

select  CHR(9)||''''
        ||regexp_replace(extractvalue(value(d),'/hint'),'''','''''')
        || ''','
from    xmltable('/*/outline_data/hint'
                passing (select     xmltype(other_xml) as xmlval
                        from        PLAN_TABLE
                        where       other_xml is not null
                        and         rownum < 2)) d;

So in this case I get following hints

    'INDEX_FFS(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',
    'OUTLINE_LEAF(@"SEL$1")',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',
    'DB_VERSION(''11.2.0.2'')',
    'OPTIMIZER_FEATURES_ENABLE(''11.2.0.2'')',
    'IGNORE_OPTIM_EMBEDDED_HINTS',

Rest of the procedure remains the same. You can use either V$SQL_PLAN and V$SQL or DBA_HIST_SQL_PLAN and DBA_HIST_SQLTEXT where ever the query is present so that profile will get generated.

Following PLSQL code can be used

declare
    ar_profile_hints sys.sqlprof_attr;
begin
    ar_profile_hints := sys.sqlprof_attr(
    'BEGIN_OUTLINE_DATA',
    'IGNORE_OPTIM_EMBEDDED_HINTS',
    'OPTIMIZER_FEATURES_ENABLE(''11.2.0.2'')',
    'DB_VERSION(''11.2.0.2'')',
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'OUTLINE_LEAF(@"SEL$1")',
    'INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',
    'END_OUTLINE_DATA');
    for sql_rec in (
    select t.sql_id, t.sql_text
    from v$sql t, v$sql_plan p
    where t.sql_id = p.sql_id
    and p.sql_id = '&SQL_ID_TO_FIX'
    and p.plan_hash_value = &BAD_PLAN_HASH_VALUE
    and p.parent_id is null
)
loop
        DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
    sql_text    => sql_rec.sql_text,
    profile     => ar_profile_hints,
    name        => 'PROFILE_&&SQL_ID_TO_FIX');

end loop;
end;
/
Enter value for sql_id_to_fix: cj4sqr25b6b8k
Enter value for bad_plan_hash_value: 2966233522
Enter value for sql_id_to_fix: cj4sqr25b6b8k

PL/SQL procedure successfully completed.

We basically tried to get the hints of a different plan (using index) just by using “explain plan for” to get the required hints from PLAN_TABLE. We then forced those hints on original SQL to create a profile so that original SQL will start using index.

Hope above procedure if clear in understanding !!

ORA-00600: internal error code, arguments: [kkdlGetCkyName1] – On ADG After Renaming Index

I encountered wired error on my Active Dataguard (ADG) instance after renaming an index on primary

Here is a simple test case to reproduce the issue

On Primary:

SQL>create table T as select * from dba_objects;

Table created.

SQL>create index T_IDX on T(data_object_id);

Index created.

SQL>exec DBMS_STATS.GATHER_TABLE_STATS('ORACLE_DBA','T');

PL/SQL procedure successfully completed.

SQL>exec DBMS_STATS.GATHER_INDEX_STATS('ORACLE_DBA','T_IDX');

PL/SQL procedure successfully completed.

SQL>explain plan for
  2  select count(1) from T where data_object_id > 65000;

Explained.

SQL>@display

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 293504097

---------------------------------------------------------------------------
| Id  | Operation      | Name  | Rows  | Bytes | Cost (%CPU)| Time      |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |    1 |    5 |    2   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE   |      |    1 |    5 |           |      |
|*  2 |   INDEX RANGE SCAN| T_IDX | 29118 |   142K|    2   (0)| 00:00:01 |
---------------------------------------------------------------------------

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

   2 - access("DATA_OBJECT_ID">65000)

14 rows selected.

SQL>select count(1) from T where data_object_id > 65000;

  COUNT(1)
----------
     28337

On Standby:

 

SQL>select count(1) from T where data_object_id > 65000;

  COUNT(1)
----------
     28337

Now rename the index on primary

On Primary:

SQL>alter index T_IDX rename to T_IDX1;

Index altered.

SQL>

Now run the same query on standby

On Standby:

SQL>select count(1) from T where data_object_id > 65000;
select count(1) from T where data_object_id > 65000
                     *
ERROR at line 1:
ORA-00600: internal error code, arguments: [kkdlGetCkyName1], [1622537], [],
[], [], [], [], [], [], [], [], []

DCYYZ1>

This is happening for following version

SQL>select * from PRODUCT_COMPONENT_VERSION;

PRODUCT                  VERSION                  STATUS
---------------------------------------- ---------------------------------------- ----------------------------------------
NLSRTL                     11.2.0.2.0                  Production
Oracle Database 11g Enterprise Edition     11.2.0.2.0                  64bit Production
PL/SQL                     11.2.0.2.0                  Production
TNS for Linux:                 11.2.0.2.0                  Production

Solution: Oracle is working on Bug 13035388: ORA 600 [KKDLGETCKYNAME1] IN ADG for fixing this issue

Workaround: Rename back the index to original name. Or drop and recreate the index.

Hope this helps !!

Fixing SQL Plans: The hard way – Part 2

In my previous article – http://avdeo.com/2012/06/20/fixing-sql-plans-the-hard-way-part-1/ – I showed you a way to fix query plan if you have a good plan available for that query in some other similar prod DB.

But what if you don’t have any other prod DB, or this query is so unique that it runs in only 1 database.

Fixing such SQLs needs deeper look into the SQL and the plan.

We have various methods to fix those SQLs like:-

- Creating Index

- Adding Hint (may be at the code level).

- Gathering stats etc.

Lets say you have all index in place, but query is not picking the index. Again, there could be many reasons why index is not picked by the query. Common one includes incorrect stats or missing stats for index. Or optimizer_index_cost_adj value is too high.

Changing any parameters or gathering stats or any changes to optimizer environment can have adverse affect to other queries.

Atleast in prod its not advisable to change any of optimizer environment.

One of the best way to fix queries in such scenario is to use hint so that index will be picked by CBO (cost based optimizer).

Having said that even if we use hints in query for CBO to pick the right index, how are we going to push such plan to actual query. We cannot (and should not) change application code and put a hint in application code.

Query should essentially remains the same but it should pick the index.

Fixing query using Hints

We want a solution where we will put hints in a query and generate the desired plan. We want original SQL ID to pick our newly generated plan.

Here is the example:

SQL>create table T as select * from dba_objects;

Table created.

SQL>col name format a30;
SQL>col value format a30;
SQL>select name, value from v$parameter where name = 'optimizer_index_cost_adj';

NAME                   VALUE
------------------------------ ------------------------------
optimizer_index_cost_adj       1

SQL>alter session set optimizer_index_cost_adj = 10000;

Session altered.

SQL>create index T_IDX on T(data_object_id);

Index created.

SQL>

SQL>exec dbms_stats.gather_table_stats('ORACLE_DBA','T');

PL/SQL procedure successfully completed.

SQL>exec dbms_stats.gather_index_stats('ORACLE_DBA','T_IDX');

PL/SQL procedure successfully completed.

SQL>select count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>select count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>select sql_id from v$sql where sql_text like 'select count(1) from T where data_object_id > 65000 and status%';

SQL_ID
-------------
cj4sqr25b6b8k

SQL>@explain
Enter SQL ID:- cj4sqr25b6b8k

PL/SQL procedure successfully completed.

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID    cj4sqr25b6b8k, child number 0
-------------------------------------
select count(1) from T where data_object_id > 65000 and status = 'VALID'

Plan hash value: 2966233522

---------------------------------------------------------------------------
| Id  | Operation       | Name | Rows  | Bytes | Cost (%CPU)| Time      |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |      |      |   258 (100)|      |
|   1 |  SORT AGGREGATE    |      |    1 |    11 |           |      |
|*  2 |   TABLE ACCESS FULL| T      | 17690 |   190K|   258   (1)| 00:00:04 |
---------------------------------------------------------------------------

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

   2 - filter(("STATUS"='VALID' AND "DATA_OBJECT_ID">65000))

19 rows selected.

SQL>

Now we know that since optimizer_index_cost_adj value is too high, it will not try to use index T_IDX on data_object_id column, because query will be expensive.

But what if we want our query to use the index. This may not be a good test case, but there could be scenario where using index will actually improve the performance of query. CBO may not be able to find that (based on cost), but we know that.

So lets force a index usage using a hint.

SQL>select /*+ index(T T_IDX) */ count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>select /*+ index(T T_IDX) */ count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>select sql_id from v$sql where sql_text like 'select /*+ index(T T_IDX) */ count(1) from T where data_object_id > 65000 and status%';

SQL_ID
-------------
dut61hdv6b12t

SQL>@explain
Enter SQL ID:- dut61hdv6b12t

PL/SQL procedure successfully completed.

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID    dut61hdv6b12t, child number 0
-------------------------------------
select /*+ index(T T_IDX) */ count(1) from T where data_object_id >
65000 and status = 'VALID'

Plan hash value: 1789076273

--------------------------------------------------------------------------------------
| Id  | Operation             | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |         |         |         |     267K(100)|         |
|   1 |  SORT AGGREGATE          |         |       1 |      11 |          |         |
|*  2 |   TABLE ACCESS BY INDEX ROWID| T     | 17690 |     190K|     267K  (1)| 00:53:31 |
|*  3 |    INDEX RANGE SCAN         | T_IDX | 35380 |         |    8253   (1)| 00:01:40 |
--------------------------------------------------------------------------------------

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

   2 - filter("STATUS"='VALID')
   3 - access("DATA_OBJECT_ID">65000)

22 rows selected.

SQL>

We can see that using index hint here, it has forced a plan to use index.
Now, we can easily get the required query hints from v$sql_plan view as we have other_xml column.
You can refer to the same SQLs as I mentioned in previous post (http://avdeo.com/2012/06/20/fixing-sql-plans-the-hard-way-part-1/) and get the required hints

select  CHR(9)||''''
        ||regexp_replace(extractvalue(value(d),'/hint'),'''','''''')
        || ''','
from    xmltable('/*/outline_data/hint'
                passing (select     xmltype(other_xml) as xmlval
                        from        v$sql_plan
                        where       sql_id = '&GOOD_SQL_ID'
                        and         CHILD_NUMBER = &CHILD_NO
                        and         other_xml is not null)) d;

In this case GOOD_SQL_ID = dut61hdv6b12t and child number = 0

    'IGNORE_OPTIM_EMBEDDED_HINTS',
    'OPTIMIZER_FEATURES_ENABLE(''11.2.0.2'')',
    'DB_VERSION(''11.2.0.2'')',
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'OUTLINE_LEAF(@"SEL$1")',
    'INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',

Rest of the procedure remains the same. Only change I did in the below PLSQL code compared to PLSQL code in my previous post is that, I changed dba_hist_sqltext table with v$sql and dba_hist_sql_plan table with v$sql_plan view. This is because new query we ran with hints may not be part of DBA_HIST* views as they are new query and will be present in only v$ views.

Following PLSQL code can be used

declare
    ar_profile_hints sys.sqlprof_attr;
begin
    ar_profile_hints := sys.sqlprof_attr(
    'BEGIN_OUTLINE_DATA',
    'IGNORE_OPTIM_EMBEDDED_HINTS',
    'OPTIMIZER_FEATURES_ENABLE(''11.2.0.2'')',
    'DB_VERSION(''11.2.0.2'')',
    'OPT_PARAM(''_b_tree_bitmap_plans'' ''false'')',
    'OPT_PARAM(''_optim_peek_user_binds'' ''false'')',
    'OPT_PARAM(''_optimizer_connect_by_cost_based'' ''false'')',
    'OPT_PARAM(''optimizer_index_cost_adj'' 10000)',
    'OPT_PARAM(''optimizer_index_caching'' 80)',
    'OUTLINE_LEAF(@"SEL$1")',
    'INDEX_RS_ASC(@"SEL$1" "T"@"SEL$1" ("T"."DATA_OBJECT_ID"))',
    'END_OUTLINE_DATA');
    for sql_rec in (
    select t.sql_id, t.sql_text
    from v$sql t, v$sql_plan p
    where t.sql_id = p.sql_id
    and p.sql_id = '&SQL_ID_TO_FIX'
    and p.plan_hash_value = &BAD_PLAN_HASH_VALUE
    and p.parent_id is null
)
loop
        DBMS_SQLTUNE.IMPORT_SQL_PROFILE(
    sql_text    => sql_rec.sql_text,
    profile     => ar_profile_hints,
    name        => 'PROFILE_&&SQL_ID_TO_FIX');

end loop;
end;
/
Enter value for sql_id_to_fix: cj4sqr25b6b8k
Enter value for bad_plan_hash_value: 2966233522
Enter value for sql_id_to_fix: cj4sqr25b6b8k

PL/SQL procedure successfully completed.

In this case SQL_ID_TO_FIX will be our original SQL = cj4sqr25b6b8k and BAD_PLAN_HASH_VALUE will be plan hash of original SQL = 2966233522

So the code at the end that I mentioned in my previous post – http://avdeo.com/2012/06/20/fixing-sql-plans-the-hard-way-part-1/ can be run on same database instead of running on other database. Only change will be to use v$ views instead of DBA_HIST* views.

If we check the plan of original query

First purge the sql from shared pool using “sys.dbms_shared_pool.purge” so that old plan will get flushed out and than run the query to see new plan

 

SQL>@purgesql
Enter SQL_ID:- cj4sqr25b6b8k

PL/SQL procedure successfully completed.

SQL>select count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>select count(1) from T where data_object_id > 65000 and status = 'VALID';

  COUNT(1)
----------
     32103

SQL>@explain
Enter SQL ID:- cj4sqr25b6b8k

PL/SQL procedure successfully completed.


PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID    cj4sqr25b6b8k, child number 0
-------------------------------------
select count(1) from T where data_object_id > 65000 and status = 'VALID'

Plan hash value: 1789076273

--------------------------------------------------------------------------------------
| Id  | Operation             | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |         |         |         |     267K(100)|         |
|   1 |  SORT AGGREGATE          |         |       1 |      11 |          |         |
|*  2 |   TABLE ACCESS BY INDEX ROWID| T     | 17690 |     190K|     267K  (1)| 00:53:31 |
|*  3 |    INDEX RANGE SCAN         | T_IDX | 35380 |         |    8253   (1)| 00:01:40 |
--------------------------------------------------------------------------------------

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

   2 - filter("STATUS"='VALID')
   3 - access("DATA_OBJECT_ID">65000)

Note
-----
   - SQL profile PROFILE_cj4sqr25b6b8k used for this statement


25 rows selected.

SQL>

 

We basically tried to simulate a different plan (using index) to get the required hints from V$SQL_PLAN. We then forced those hints on original SQL to create a profile so that original SQL will start using index.

Hope above procedure if clear in understanding !!

Sessions and Processes Parameters – Oracle 11g

Oracle has changed the way it derives sessions and processes parameters in database.

In 10g, oracle used to derive sessions parameter from processes parameter using following formula

(1.1 * PROCESSES) + 5

In 11g R1 onwards it changed to

(1.5 * PROCESSES) + 22

It has another rule though:

If we set lower value of sessions parameters than derived value, Oracle will automatically bump it to above derived value.
If we set higher value of sessions parameters than derived value, Oracle will consider our set value

So it always takes which ever is higher

SQL>select name, value from v$spparameter where name in ('sessions','processes');

NAME                   VALUE
------------------------------ ----------
processes               4500
sessions               5000

SQL>select name, value from v$parameter where name in ('sessions','processes');

NAME                   VALUE
------------------------------ ----------
processes               4500
sessions               6776

We can see derived value (4500*1.5)+22=6776 is greater than set value of 5000 in spfile. So its taking derived value for this parameter

Lets change the value in spfile to 7000

SQL> alter system set sessions = 7000 scope=spfile;

System altered.

<< Bounce DB >>
SQL>select name, value from v$spparameter where name in ('sessions','processes');

NAME                   VALUE
------------------------------ ----------
processes               4500
sessions               7000

SQL>select name, value from v$parameter where name in ('sessions','processes');

NAME                   VALUE
------------------------------ ----------
processes               4500
sessions               7000

Now you can see its taking the set value, because set value of more than derived value of 6776.

Hope this helps !!

References:

Sessions parameter in 10.2 – http://docs.oracle.com/cd/E11882_01/server.112/e25513/initparams230.htm#REFRN10197

Sessions parameter in 11.1 – http://docs.oracle.com/cd/B19306_01/server.102/b14237/initparams191.htm#i1133629

Sessions parameter in 11.2 – http://docs.oracle.com/cd/E11882_01/server.112/e25513/initparams230.htm#i1133629

Follow

Get every new post delivered to your Inbox.

Join 221 other followers