IN ORACLE MILIEU …

Beyond Knowledge

Inside Index block – Oracle Database 11g

I ran into a scenario where I really wanted to check whats inside the leaf block of an index.

This post is to dig inside the index leaf block and find out various facts stored inside a block.

We will start with taking a dump of a block

SYS.ORCL>alter session set tracefile_identifier='INDEX_BLOCK_DUMP';
Session altered.
SYS.ORCL>alter system dump datafile 292 block 245790;
System altered.
SYS.ORCL>

You will get a trace file having name something like “<DB_NAME>_ora_<PID>_INDEX_BLOCK_DUMP.trc” in your trace directory

Even if you dont use tracefile_identifier, you can still see the name of the dump file using following query

SYS.ORCL>select value from v$diag_info where name = 'Default Trace File';
VALUE
-----------------------------------------------------------------
/dumps-01/diag/rdbms/orcl_b/orcl/trace/orcl_ora_11416.trc
1 row selected.
SYS.ORCL>

At the start of the block you will see following details

*********************************************************************
Start dump data blocks tsn: 17 file#:292 minblk 245790 maxblk 245790
Block dump from cache:
Dump of buffer cache at level 4 for tsn=17, rdba=1224982558
Block dump from disk:
buffer tsn: 17 rdba: 0x4903c01e (292/245790)
scn: 0x06ca.4d4684c7 seq: 0x01 flg: 0x04 tail: 0x84c70601
frmt: 0x02 chkval: 0xe178 type: 0x06=trans data
*********************************************************************

tsn –> Tablespace Number = 17
file# –> File # = 292
minblk & maxblk –> Min block and Max block numbers = 245790
rdba –> Relative data block address. Its value is 0x4903c01e in hexa decimal and 1224982558 in decimal.
We can get rdba using dbms_utility.make_data_block_address

SYS.ORCL>select dbms_utility.make_data_block_address(292,245790) from dual;
DBMS_UTILITY.MAKE_DATA_BLOCK_ADDRESS(292,245790)
------------------------------------------------
 1224982558

After above lines we have dump of memory. You will see a line something as below

Dump of memory from 0x00002B5C26C2DA00 to 0x00002B5C26C2FA00

So at each address it will print the content. Its very difficult to read this format.

2B5C26C2DA00 0000A206 4903C01E 4D4684C7 040106CA [.......I..FM....]
2B5C26C2DA10 0000E178 00000002 000551A1 4CFB5368 [x........Q..hS.L]
2B5C26C2DA20 000006CA 0032001D 4903C009 000F0046 [......2....IF...]
2B5C26C2DA30 00221BD6 01460CC8 0001F2FC 06CAC000 [.."...F.........]
2B5C26C2DA40 40FE45EE 001D00EE 0008E5F9 0086EC85 [.E.@............]
2B5C26C2DA50 003EC8BA 06CA8000 40FA9BFF 00000000 [..>........@....]
2B5C26C2DA60 00000000 00000000 00000000 00000000 [................]
2B5C26C2DA70 00000000 00100040 0027C6F8 0085DF28 [....@.....'.(...]

But once this memory dump is complete, we can see some readable information stored in index block. Here is what comes next

seg/obj: 0x551a1 csc: 0x6ca.4cfb5368 itc: 29 flg: E typ: 2 - INDEX
 brn: 0 bdba: 0x4903c009 ver: 0x01 opc: 0
 inc: 0 exflg: 0

seg/obj is the object_id. 0x551a1 = 348577 in 348577 in decimal.

SYS.ORCL>select object_name from dba_objects where data_object_id = 348577;
OBJECT_NAME
-------------------------
UNIQ_FPM_MSG_WHSE_CAT_ID
1 row selected.

This is the index for which we dumped the block.

csc: 0x6ca.4cfb5368 –> This is the comit SCN number of the block
itc: 29 –> Number of interested transaction list(ITL) entry in the block.
typ: 2 – INDEX –> Indicates this is an index block

Next comes the Interested Transaction List(ITL) entries. We had 29 ITL entries as indicated above by itc: 29

Itl  Xid                 Uba                Flag Lck   Scn/Fsc
0x01 0x0046.00f.00221bd6 0x01460cc8.f2fc.01 CB-- 0 scn 0x06ca.40fe45ee
0x02 0x00ee.01d.0008e5f9 0x0086ec85.c8ba.3e C--- 0 scn 0x06ca.40fa9bff
0x03 0x0000.000.00000000 0x00000000.0000.00 ---- 0 fsc 0x0000.00000000
0x04 0x0040.010.0027c6f8 0x0085df28.cb0f.0b C--- 0 scn 0x06ca.40fb14e2
0x05 0x00f0.017.00090d20 0x37860557.c767.36 C--- 0 scn 0x06ca.40fb15a7
0x06 0x013d.020.0002205c 0x3802952d.202e.26 C--- 0 scn 0x06ca.40fd6d83
0x07 0x001d.012.0049340a 0x38063ebe.c0d6.48 C--- 0 scn 0x06ca.40fd6e3e
0x08 0x00b5.018.000e6ee9 0x384279bf.95f1.07 C--- 0 scn 0x06ca.40fe4336
0x09 0x010e.007.000a4514 0x0088535e.b343.19 C--- 0 scn 0x06ca.40fa9b08
0x0a 0x00df.00f.0009db56 0x014571c8.e27e.0a C--- 0 scn 0x06ca.40fe446d
0x0b 0x00f4.00b.00092665 0x37c65073.c520.27 C--- 0 scn 0x06ca.4100f955
0x0c 0x0129.017.00035f0e 0x0187980c.2a1a.04 C--- 0 scn 0x06ca.4100fa77
0x0d 0x007a.002.0010db5b 0x38458cb5.5bbd.20 C--- 0 scn 0x06ca.41168fad
0x0e 0x0069.00e.0017dd7d 0x3807b2e2.1390.30 C--- 0 scn 0x06ca.411691b0
0x0f 0x00d7.006.0009a7ee 0x38417fbc.dd00.19 C--- 0 scn 0x06ca.41169313
0x10 0x0181.003.000170b0 0x00c78696.190d.2b C--- 0 scn 0x06ca.41169bc6
0x11 0x00bd.014.000d2ddf 0x3805ab0d.72f9.19 C--- 0 scn 0x06ca.41169ecc
0x12 0x0001.019.00634f4c 0x3802e302.4fd4.0d C--- 0 scn 0x06ca.4116ab9e
0x13 0x0035.014.003a9a1d 0x014691a5.cda1.3d C--- 0 scn 0x06ca.4116ad76
0x14 0x017a.017.00018a03 0x37c4d213.17c4.0d C--- 0 scn 0x06ca.4116b388
0x15 0x006a.012.0017f16a 0x37878e97.329f.2b C--- 0 scn 0x06ca.4165b46c
0x16 0x00f0.01b.00090d4e 0x3846d24e.c76b.36 C--- 0 scn 0x06ca.4165b57c
0x17 0x0168.004.00019960 0x00c77875.18ab.08 C--- 0 scn 0x06ca.4165b7a5
0x18 0x0104.002.0006b6fd 0x38401fbe.a734.0e C--- 0 scn 0x06ca.4165b951
0x19 0x0044.018.00275fa5 0x38417b7e.cf86.3c C--- 0 scn 0x06ca.41035e03
0x1a 0x0127.01c.0006d44b 0x008602ca.5584.04 C--- 0 scn 0x06ca.40f95f82
0x1b 0x00ba.004.000dbc9a 0x3786425e.74f4.32 C--- 0 scn 0x06ca.40f96071
0x1c 0x0194.004.00014665 0x38464daf.1aca.46 C--- 0 scn 0x06ca.40f9c995
0x1d 0x0014.00c.0066b5d6 0x01865ee3.e323.08 C--- 0 scn 0x06ca.40f9ca31

Itl –> Slot Id of interested transaction
Xid –> Transaction ID od transaction occupying that ITL
Uba –> Undo block address
Flag –> State for current transaction (C – Commited)
Lck –> Number of Lock held by the transacion
Scn/Fsc –> SCN number of the current transaction

Once ITL list is displayed, next comes the leaf block dump.
At the start of leaf block dump it gives the header information of the block

Leaf block dump
===============
header address 47674787290348=0x2b5c26c2dcec     => Header start and end address
kdxcolev 0                                       => Index Level. 0 represent leaf block
KDXCOLEV Flags = - - -
kdxcolok 0                                       => Indicates that no block transaction happening right now
kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y    => Internal Operation code
kdxconco 3                                        => Index Column Count
kdxcosdc 8                                        => Count of index structural changes involving block
kdxconro 48                                       => Number of index records in this block
kdxcofbo 132=0x84                                 => Offset from start to the beginning of free space
kdxcofeo 1435=0x59b                               => Offset to end of free space (ie. first portion of block containing index data)
kdxcoavs 1303                                     => Available space in the block (effective space between kdxcofbo and kdxcofeo)
kdxlespl 0                                        => Bytes of uncommited data at the time of block split that have been cleaned out
kdxlende 0                                        => Number of deleted entries
kdxlenxt 830726637=0x3183e1ed                     => Pointer to next leaf block
kdxleprv 830721132=0x3183cc6c                     => Pointer to previous leaf block
kdxledsz 10                                       => Deleted space
kdxlebksz 7384                                    => Size of usable block space

After header, comes the index information as shown below

row#0[3602] flag: ------, lock: 0, len=61, data:(10): 00 0c 13 af 37 47 af ee 00 05
col 0; len 26; (26):
 35 35 39 32 35 38 5f 31 33 35 33 32 35 30 31 34 33 5f 36 38 5e 32 34 31 34
 30
col 1; len 4; (4): 4c 45 4a 31
col 2; len 16; (16): 43 68 61 6e 67 65 50 69 63 6b 53 74 61 74 75 73
row#1[3663] flag: ------, lock: 0, len=57, data:(10): 00 0c 13 af 37 47 af 24 00 42
col 0; len 26; (26):
 35 35 39 22 35 38 5f 31 33 32 33 32 35 30 32 32 33 5f 36 38 5f 32 34 31 34
 30
col 1; len 4; (4): 4c 45 4f 33
col 2; len 12; (12): 50 69 63 6b 43 6f 6d 70 6c 65 74 65

Here is what above values mean

row#0 -> First row in the block. Row starts from 0
[3602] -> Starting location within the block
flag: -> Flag to give information above the record in index leaf block. If you delete a row, the flag will be marked D, meaning deleted.
lock -> Lock information at row level
len -> Total length of a index record
data -> I guess this should be the length of rowid which is following this “00 0c 13 af 37 47 af ee 00 05″

Each row in this index has 3 columns. Columns start with col0. So we have column information in col0, col1 and col2

col0 -> First column
len -> Length of the column. 26 in this case.
Value after that is the actual value in base 16 stored in the block.

We can actually get real value for the values stored above using following function SQL.
You need to pass the hex value seen in block dump to a function dbms_stats.convert_raw_value and it returns the required values.

Lets say we want to find out the value for first row (Value for each column in first row)

SQL>var col1 varchar2(1000);
SQL>exec dbms_stats.convert_raw_value(replace('35 35 39 32 35 38 5f 31 33 32 33 32 35 30 30 32 33 5f 36 38 5f 32 34 31 34 30',' ',''),:col1);
PL/SQL procedure successfully completed.
SQL>var col2 varchar2(10);
SQL>exec dbms_stats.convert_raw_value(replace('4c 45 4a 31',' ',''),:col2);
PL/SQL procedure successfully completed.
SQL>var col3 varchar2(100);
SQL>exec dbms_stats.convert_raw_value(replace('43 68 61 6e 67 65 50 69 63 6b 53 74 61 74 75 73',' ',''),:col3);
PL/SQL procedure successfully completed.
COL1
-----------------------------
559258_1323250023_68_24140

COL2
-----------------------------
ABC

COL3
-----------------------------
EmployeeStatus
SQL>

Similarly you can write your own PLSQL code and find out all the values stored inside an index block.

Hope this helps !!

Oracle SQL Plan Management – Part 3

SQL Plan Management – Oracle Database 11g

You have seen the first 2 parts of SQL plan baselines

Part 1 – http://avdeo.com/2011/06/02/oracle-sql-plan-management-part-1/
Part 2 – http://avdeo.com/2011/06/07/oracle-sql-plan-management-%e2%80%93-part-2/

In part 1 we have seen information about SPM and how to automatically capture the baseline
In part 2 we have seen how to manually capture the baseline and the affect of using FIXED variable
In part 3 we will now see, how to import the baselines for SQL from some remote database and also how to set a specific plan for a query using hint and then creating baseline for that

Importing baseline from remote database to our database

Lets say we have a database and a table T in the database.

SQL> desc T
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 COL1                                               NUMBER
 COL2                                               VARCHAR2(50)
 COL3                                               DATE

No Index exists on this table.

SQL> select count(1) from T;

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

SQL> select col1, count(1) from T group by col1;

      COL1   COUNT(1)
---------- ----------
         1          1
         2          3
         3    2097152

SQL> explain plan for
  2  select * from T where col1 = 1;

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  |      |  1048K|    11M|  1424   (6)| 00:00:18 |
|*  1 |  TABLE ACCESS FULL| T    |  1048K|    11M|  1424   (6)| 00:00:18 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

13 rows selected.

SQL>

Lets create a baseline for this query manually.

Find the SQL ID using

SQL>select sql_id from v$sql where sql_text = 'select * from T where col1 = 1';

SQL_ID
-------------
5pvxxjg6n7mrp

SQL>

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '5pvxxjg6n7mrp');
END;
/

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID');
Enter value for sql_id: 5pvxxjg6n7mrp
old   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID')
new   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='5pvxxjg6n7mrp')

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO

SQL>

So now the baseline is created for this SQL.

If we check the plan it will show us Full Table Scan.

Now lets create an index.

SQL> create index T_IDX on T(col1);

Index created.

SQL> exec DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> '&owner', TABNAME => '&tabname', DEGREE => 6, GRANULARITY => 'ALL' ,CASCADE => TRUE , METHOD_OPT => 'FOR ALL INDEXED COLUMNS SIZE 254');
Enter value for owner: ADVAITD_DBA
Enter value for tabname: T

PL/SQL procedure successfully completed.

SQL>

lets see the plan for same query again. Since we have created an index on col1 of table T, when using value 1 we expect an index to be used rather than FTS.

SQL> explain plan for
  2  select * from T where col1 = 1;

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  |      |  1048K|    11M|  1424   (6)| 00:00:18 |
|*  1 |  TABLE ACCESS FULL| T    |  1048K|    11M|  1424   (6)| 00:00:18 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920f94ecae5c" used for this statement

17 rows selected.

SQL>

Here we clearly see baseline is getting used and it still uses FTS. This is a plan stability feature in 11g. No matter what changes we do, if a baseline already exists for a query and even if you create an index, it wont use that index unless you create a new baseline for that. As you know by now, new baseline will get created automatically only if you set OPTIMIZER_CAPTURE_SQL_PLAN_BASELINES parameters is set to true. If this is set to false you have to create baseline manaully using DBMS_SPM package as shown above.

At this point of time we have an option to create a baseline and using DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE

But instead of using that we will try to export and import a baseline from some other DB having same query and tables.

So lets say, I have another database having same table and running same query. But that query is using correct index on col1. So we will see how to get that baseline exported and imported to our DB.
In real time situation, we will get several cases where in same set of queries are running good in some other databases but thay are having bad plan in our databaes. So instead of trying to get the plan right using traditional methods, we can simply export and import the baseline of that query from some other DB where its performing better and plans will be fixed in our DB without much effort.
So on my new DB, I have a correct plan as indicated below

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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            |       |  2359K|   139M|   307   (1)| 00:00:04 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  2359K|   139M|   307   (1)| 00:00:04 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  2379K|       |    47   (3)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

Above query is using the baseline SYS_SQL_PLAN_1d83920fae82cf72

We will export and import this baseline in our database now. Following steps needs to be done to export and import the baseline

1) Create a staging table

exec DBMS_SPM.CREATE_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA');

STGTAB –> Name of staging table. You can give any name which is not used till now. A table with this name will get created.
ADVAITD_DBA –> Owner of the table. Table will get created in this schema.

2) Pack baseline into this table

declare
x number;
begin
x := DBMS_SPM.PACK_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA', sql_handle => 'SYS_SQL_1447ba3a1d83920f', plan_name => 'SYS_SQL_PLAN_1d83920fae82cf72' );
end;
/

3) Export the staging table from source DB where you have baseline packed
exp userid=advaitd_dba@new_db tables=’STGTAB’ file=STGTAB.dmp log=STGTAB.log

advaitd.desktop$ exp userid=advaitd_dba@new_db tables='STGTAB' file=STGTAB.dmp log=STGTAB.log

Export: Release 10.2.0.2.0 - Production on Sun Aug 7 06:15:39 2011

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Password:

Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options
Export done in US7ASCII character set and AL16UTF16 NCHAR character set
server uses UTF8 character set (possible charset conversion)

About to export specified tables via Conventional Path ...
. . exporting table                         STGTAB          1 rows exported
Export terminated successfully without warnings.
advaitd.desktop$

4) Import the table into the DB where you want to put the baseline
imp userid=advaitd_dba@our_db file=STGTAB.dmp log=STGTAB.log full=y

advaitd.desktop$ imp userid=advaitd_dba@our_db file=STGTAB.dmp log=STGTAB.log full=y

Import: Release 10.2.0.2.0 - Production on Sun Aug 7 06:20:54 2011

Copyright (c) 1982, 2005, Oracle.  All rights reserved.

Password:

Connected to: Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production
With the Partitioning, Oracle Label Security, OLAP, Data Mining
and Real Application Testing options

Export file created by EXPORT:V10.02.01 via conventional path
import done in US7ASCII character set and AL16UTF16 NCHAR character set
import server uses UTF8 character set (possible charset conversion)
. importing ADVAITD_DBA's objects into ADVAITD_DBA
. importing ADVAITD_DBA's objects into ADVAITD_DBA
. . importing table                       "STGTAB"          1 rows imported
Import terminated successfully without warnings.
advaitd.desktop$

5) Unpack the staging table

declare
x number;
begin
x := DBMS_SPM.UNPACK_STGTAB_BASELINE('STGTAB', 'ADVAITD_DBA');
end;
/

Now we can see 2 baseline.
- One baseline which was already exisitng and doing FTS
- One we imported just now which is using index

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID');
Enter value for sql_id: 5pvxxjg6n7mrp
old   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='&SQL_ID')
new   1: select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines where signature IN (SELECT exact_matching_signature FROM v$sql WHERE sql_id='5pvxxjg6n7mrp')

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES YES NO

Now, if we check the plan for same query, we should be able to see index getting used and it will use the plan SYS_SQL_PLAN_1d83920fae82cf72 which we imported from other DB

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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            |       |  1048K|    11M|    46   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|    46   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |    21   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

Creating baseline for user created plans

Lets go back to original situation where query on the table is doing a FTS. At this point instead of getting a profile from some other DB, lets create a correct profile using hints.

I am flushing the shared_pool so as to remove any exising plans from shared_pool

SQL> alter system flush shared_pool;

System altered.

SQL> /

System altered.

SQL>

Lets use Index hint here and see if the query uses index.

SQL> select /*+ index(T_IDX T) +*/ * from T where col1 = 1;

      COL1 COL2                                               COL3
---------- -------------------------------------------------- -----------
         1                                                    10-JUL-2011

SQL> explain plan for
  2  select /*+ index(T_IDX T) +*/ * from T where col1 = 1;

Explained.

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            |       |  1048K|    11M|   465   (2)| 00:00:06 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|   465   (2)| 00:00:06 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |   209   (2)| 00:00:03 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

14 rows selected.

SQL>

So using a hint serve the purpose.
In real life situation SQLs are usually part of modules and codes. So using hint at SQL level, its not possible to implement the hint in code. Also, using hints in SQL at code level is not a good practice for obvious reason.

If we create a baseline based on hints than baseline will be automatically used for that SQL and plan will be same as that using hint.

Procedure is simple, just use the hint and run sql couple of times. One you do that, the plan will be automatically stored in shared_pool.

Find a SQL ID

SQL> select sql_id from v$sql where sql_text like 'select /*+ index(T_IDX T) +*/ * from%';

SQL_ID
-------------
50tsvs88gx382

Use DBMS_SPM.load_plans_from_cursor_cache to create baseline from cursor cache.

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '50tsvs88gx382');
END;
/

At this point we have 2 SQLs -

50tsvs88gx382 – using index hint
5pvxxjg6n7mrp – Original query without using hint. This is doing FTS

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines ;

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES NO  NO
SYS_SQL_3f0d350a1c24a8d4       SYS_SQL_PLAN_1c24a8d4ae82cf72  YES YES NO

SQL>

SQL_HANDLE – SYS_SQL_1447ba3a1d83920f is for original query without hint. This was having only 1 baseline, but when we loaded the baseline for query 50tsvs88gx382 which is using hint, it automatically created the baseline for original query. 11g is smart enought to filter the hints and create a baseline for original query.

At this stage you have 2 option

1) Accept plan SYS_SQL_PLAN_1d83920fae82cf72 for SQL_HANDLE SYS_SQL_1447ba3a1d83920f
2) Link sql plan SYS_SQL_PLAN_1c24a8d4ae82cf72 of SQL_HANDLE SYS_SQL_3f0d350a1c24a8d4 to SQL_HANDLE SYS_SQL_1447ba3a1d83920f

You already know option 1.

Lets see option 2.

To link the plan of some other SQL_HANDLE (SYS_SQL_3f0d350a1c24a8d4) to our SQL_HANDLE (SYS_SQL_1447ba3a1d83920f), you can use same procedure dbms_spm.load_plans_from_cursor_cache

DECLARE
  l_plans_loaded  PLS_INTEGER;
BEGIN
  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
    sql_id => '50tsvs88gx382',
    PLAN_HASH_VALUE => 470836197,
    SQL_HANDLE => 'SYS_SQL_1447ba3a1d83920f');
END;
/

PL/SQL procedure successfully completed.

In above procedure,
- SQL_ID is the new SQL_ID having hints.
- PLAN_HASH_VALUE is the plan hash value of SQL with hints. This is the plan we want to assign to our SQL_HANDLE
- SQL_HANDLE is the name of our SQL Handle to which we want to assign the plan

After assigning the plan, you will see the status as below.

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED from dba_sql_plan_baselines ;

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES YES NO
SYS_SQL_3f0d350a1c24a8d4       SYS_SQL_PLAN_1c24a8d4ae82cf72  YES YES NO

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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            |       |  1048K|    11M|   465   (2)| 00:00:06 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |  1048K|    11M|   465   (2)| 00:00:06 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |  1048K|       |   209   (2)| 00:00:03 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

Hope this helps !!

Oracle SQL Plan Management – Part 2

SQL Plan Management – Oracle Database 11g

In SPM part 1 we saw information about SPM and how to automatically capture the baseline.
In this part we will see how to manually capture the baseline and the affect of using FIXED variable.

Capturing Baseline Manually

Occasionally we have to to capture the baseline manually. This could happen while tuning the query in our database where we have optimizer_capture_sql_plan_baselines parameter set to FALSE.

We have 2 procedures available for capturing the baselines manually.

  1. LOAD_PLANS_FROM_SQLSET

  2. LOAD_PLANS_FROM_CURSOR_CACHE

Above procedures are part of DBMS_SPM packages.

LOAD_PLANS_FROM_SQLSET procedure is used mainly while doing the upgrade from Oracle database 10g to Oracle database 11g.

The procedure is to

  1. create a SQL Tuning set in 10g and pack all the SQLs in the library cache along with there plans into SQL tuning set  – Procedure DBMS_SQLTUNE.CREATE_SQLSET and DBMS_SQLTUNE.LOAD_SQLSET
  2. Then to create a staging table and load this SQL tuning set into the staging table – Procedure DBMS_SQLTUNE.CREATE_STGTAB_SQLSET and DBMS_SQLTUNE.PACK_STGTAB_SQLSET
  3. Update database to 11g
  4. Unpack the staging table into SQL Tuning set you created before – Procedure DBMS_SQLTUNE.UNPACK_STGTAB_SQLSET
  5. Create SQL Baselines from the SQL tuning set – Procedure DBMS_SPM.LOAD_PLANS_FROM_SQLSET

LOAD_PLANS_FROM_CURSOR_CACHE is used after 11g upgrade when we have optimizer_capture_sql_plan_baselines set to false and we want to create baseline for a query in order to stablize the plan.


SQL> select col1, count(1) from T group by col1;

      COL1   COUNT(1)
---------- ----------
         1          1
         2          3
         3      98304

SQL> show parameters capture

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_capture_sql_plan_baselines boolean     FALSE

SQL>

Currently we dont have any baselines in the database.


SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

no rows selected

SQL>

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

SQL> set line 999
SQL> set pagesize 999
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  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

13 rows selected.

SQL>

Lets create a baseline manually for this statement. For creating baselines manually we need to have the statement in cache first.
Lets run the statement once and find the SQL ID.


SQL> select sql_id, sql_text from v$sql where sql_text like 'select * from T where%';

SQL_ID        SQL_TEXT
------------- ------------------------------------------------------------------------------------------
5pvxxjg6n7mrp select * from T where col1 = 1

Now we will use LOAD_PLANS_FROM_CURSOR_CACHE to create a baseline

SQL> DECLARE
  2  l_plans_loaded  PLS_INTEGER;
  3  BEGIN
  4  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
  5  sql_id => '5pvxxjg6n7mrp');
  6  END;
  7  /

PL/SQL procedure successfully completed.

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO

SQL>

So we can see that SQL Plan baseline is created as well as accepted.
We will check out the plan from baseline now, but it should be full table scan.


SQL> SET LONG 10000
SQL> select * from TABLE(DBMS_XPLAN.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920f94ecae5c'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from T where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920f94ecae5c
Enabled: YES     Fixed: NO      Accepted: YES     Origin: MANUAL-LOAD
--------------------------------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

24 rows selected.

SQL>

Now lets create an index on the table on COL1.

SQL> create index T_IDX on T(COL1);

Index created.

SQL>

Now since the baseline is used, same query will go for a full table scan even after creating an index.


SQL> explain plan for
  2  select * from T where col1 = 1;

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  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920f94ecae5c" used for this statement

17 rows selected.

SQL>

So as you can see we have a baseline SYS_SQL_PLAN_1d83920f94ecae5c getting used for this query.

Lets go ahead and try creating another baseline for this SQL. May be this time we will get better plan.

SQL ID remains the same.


SQL> DECLARE
  2  l_plans_loaded  PLS_INTEGER;
  3  BEGIN
  4  l_plans_loaded := DBMS_SPM.load_plans_from_cursor_cache(
  5  sql_id => '5pvxxjg6n7mrp');
  6  END;
  7  /

PL/SQL procedure successfully completed.

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1   YES NO  NO

And yes, we can see Oracle has created another baseline here. But ACCEPTED is still NO for the new plan.
This is because we haven’t evaluated the plan yet. If we would have set optimizer_capture_sql_plan_baselines parameter to true, it would have automatically captured and made ACCEPTED=YES after doing automatic evaluation.
Since this is false, we have to evaluate whether new plan is better or not. This process of evaluating new baseline is called evolving.

Evolving a plan actually compares the cost and several other factors like DISK_READS, BUFFER_GETS, ELAPSED_TIME etc for all the plans that are available in DBA_SQL_PLAN_BASELINES table for that particular SQL HANDLE.
So in our case when we evolve the plan it will compare plan SYS_SQL_PLAN_1d83920f94ecae5c and SYS_SQL_PLAN_1d83920fae82cf72

Just to be certain we will display the plan SYS_SQL_PLAN_1d83920fae82cf72


SQL> select * from TABLE(DBMS_XPLAN.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920fae82cf72'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from T where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920fae82cf72
Enabled: YES     Fixed: NO      Accepted: NO      Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       | 49154 |  2976K|     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

25 rows selected.

SQL>

So it is indeed using the index on the table.

Here there are 2 things again.

  1. Evolving the plan automatically – Using procedure DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE
  2. Manually accepting the plan – Using procedure ALTER_SQL_PLAN_BASELINE setting ACCEPTED=>YES

In the first method optimizer will evaluate based on all the factors (we will see those factors below) and “selects” the best plan. I have highlighted select here because optimizer is going to select from the set of existing plans, its not going to parse and create a whole other plan.
So its upto the optimizer to decide which plan is best among the available plan.

In method 2, we can decide and select which ever plan we think is correct. This based on our knowledge of the type of SQL we are running and what resources we have with us.

Can we have 2 plans in ACCEPTED status for a query?

Yes we can.

Which plan optimizer will choose out of the two?

Always the best among the two. If more than 1 plan is ACCEPTED than optimizer will choose the best plan to be used for the query.
Example if we use a query “select * from T where col = :B1″
Here we have used a bind variable. Lets say we have 2 plans with us right now
Plan 1) Full table scan
Plan 2) Index Range scan

Which plan optimizer will choose depends upon the value of bind variable.
Yes, in 11g we have something called “Adaptive Cursor Sharing”, but this is little off the topic. We will cover “Adaptive Cursor Sharing” in different article.

So if we supply value as 1 or 2 and since there is only 1 row with col1=1 and 3 rows with col1=2, optimizer will use Plan 2) Index Range Scan.
If we supply value as 3, optimizer will use Plan 1) Full table scan.

So imagine a case where we have 5-6 plans based on selectivity of different tables involved in a complex query, in that case its a good idea to keep all those plans as ACCEPTED. Optimizer will always choose the best among those based on selectivity of the data.

We have seen evolving the plan in Part 1 – http://avdeo.com/2011/06/02/oracle-sql-plan-management-part-1/
Lets set ACCEPTED=TRUE manually now.


SQL> SET SERVEROUTPUT ON
SQL> DECLARE
  2    l_plans_altered  PLS_INTEGER;
  3  BEGIN
  4    l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
  5      sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
  6      plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',
  7      attribute_name  => 'ACCEPTED',
  8      attribute_value => 'YES');
  9
 10    DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
 11  END;
 12  /
Plans Altered: 1

PL/SQL procedure successfully completed.

SQL>

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                         ENA ACC FIX
------------------------------ ------------------------------ -------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1   YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1   YES YES NO

SQL>

Lets check the explain plan for value 1.


SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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            |       | 49154 |  2976K|    56   (2)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|    56   (2)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |    10   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

So its using SYS_SQL_PLAN_1d83920fae82cf72 baselines.

What is the affect of using FIXED=YES ?

Fixed attribute will fix the plan for a query. So even if we have a better plan, the query will still not be using it. It is like disabling (ENABLED=NO, ACCEPTED=NO) all the plans for that SQL Handle.

Let make FIXED=YES for the first baseline (SYS_SQL_PLAN_1d83920f94ecae5c) which does full table scan.
To change this attribute we can again use DBMS_SPM.ALTER_SQL_PLAN_BASELINE procedure


SQL> set serveroutput on
SQL> DECLARE
  2    l_plans_altered  PLS_INTEGER;
  3  BEGIN
  4    l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
  5      sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
  6      plan_name       => 'SYS_SQL_PLAN_1d83920f94ecae5c',
  7      attribute_name  => 'FIXED',
  8      attribute_value => 'YES');
  9
 10    DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
 11  END;
 12  /
Plans Altered: 1

PL/SQL procedure successfully completed.

SQL>

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                           ENA ACC FIX
------------------------------ ------------------------------ -------------------------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1                     YES YES YES
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1                     YES YES NO

SQL>

Lets check out the explain plan for “select * from T where col1 = 1″ query


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  |      | 49154 |  2976K|   260   (2)| 00:00:04 |
|*  1 |  TABLE ACCESS FULL| T    | 49154 |  2976K|   260   (2)| 00:00:04 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920f94ecae5c" used for this statement

17 rows selected.

SQL>

So if you see now, same query which was earlier using index scan has now shifted to full table scan. Thats the magic of SPM !!
I would recommend not using FIXED attribute at all. You are just killing the functionality provided by 11g.

However, if we make both plans as FIXED then ??


set serveroutput on                                           
DECLARE                                                       
  l_plans_altered  PLS_INTEGER;                               
BEGIN                                                         
  l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(        
    sql_handle      => 'SYS_SQL_1447ba3a1d83920f',            
    plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',       
    attribute_name  => 'FIXED',                               
    attribute_value => 'YES');                                
                                                              
  DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
END;                                                          
/                                                             

SQL> select sql_handle, plan_name, sql_text, enabled, accepted, fixed from dba_sql_plan_baselines;

SQL_HANDLE                     PLAN_NAME                      SQL_TEXT                                           ENA ACC FIX
------------------------------ ------------------------------ -------------------------------------------------- --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  select * from T where col1 = 1                     YES YES YES
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  select * from T where col1 = 1                     YES YES YES

SQL> explain plan for
  2  select * from T where col1 = 1;

Explained.

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            |       | 49154 |  2976K|     6   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     | 49154 |  2976K|     6   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX | 49154 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

It will again go back to best among the fixed plans.

In the next part, I will cover how to transfer the baseline from one database to another and also how to set a specific plan for a query using hint and then creating baseline for that.

Hope this helps !!

 

Oracle SQL Plan Management – Part 1

SQL Plan Management – Oracle Database 11g

SQL Plan Management or SPM is a new feature introduced in 11g. We will take a detailed look at SPM in the following sessions.
Before starting with practical example lets see some theory.

What is SQL Plan Management?

SPM is a new feature introduced in Oracle database 11g which stores the baseline (Plans + Hints) inside database.
SPM allows an Oracle DBA to capture and preserve the most efficient execution plans for any SQL statement, thus limiting the impact of refreshed optimizer statistics, changes to existing applications, and even upgraded database versions

Why do we need SPM?

SPM is basically used for plan stability. If you have a database environment with several queries running, you always want your queries to run the way they are running irrespective of the changes that you make in your environment.
Example, If you upgrade your database, your queries might behave differently because of the change in optimizer or change in environment and at initial run you have to fix many queries in your production DB because of the upgrade or change in the env.
To avoid this, SPM is introduce to give you plan stability. So even after change in the environment, your queries will use same plan as it was before. More plans (Better or worse) could exists in the new environment, but optimizer is not allowed to use those plans without you confirming the plan change.

What was wrong with outlines?

Well, outlines are more about fixing the plan for a query that optimizer will use. So if there is change in environment, the plan would still be the same.
There are certain situations where you want the plan to change with change in lets say data. What if your data gets skewed over time.

Intially you had 100 of entries for month “DEC” out of total 150 entries. So it was using FTS. Now with increase in the data, there are 1 million records and entries for month “DEC” are around 1000. In that case it makes sense for optimizer to change plan and start using Index scan instead of FTS.
But since you used outline, you are forcing optimizer to go for a FTS. This is just an example situation and things can change in your environment and so outline is not a very good approach for such situations.

What was wrong with profile?

Profile is another feature introduced in 10g and can be implemented at SQL level. Profiles are better than outlines in that they are not fixing a plan through out the life of SQL. With change in the environment the plans can change even if the profile is used.

So whats the difference between profile and SQL plan baselines?

Profile is more of a advisors. They give advice to optimizer while executing the query. They provide optimizer with all the precise estimates. Profiles are more abount correcting optimizer to use correct plan when the underlying data is skewed and changed drastically.
The goal is to create best execution plan for the SQL by giving the very precise data to the optimizer. Its the optimizer who will decide what should be the explain plan based on the information/hints it has received from profile.

SQL Plan baselines works differently. It enforces the plan to be used for a SQL. Baseline does not provide and estimates or it does not help optimizer in anyway. It just tells optimizer to ignore everything and use the plan that we are giving you.
Optimizer just followes the plan provided by baselines. So here baseline is the driving mechanism for getting the correct plan.

Finally how baselines are different then outline then?

Well, baseline always keeps the optimized plans for your SQL. If there is a change in the environment you will have a new baseline created for the same SQL. Oracle will stop that baseline in the baseline history.
Depending on the baseline parameters setting in your environment, new plan will be automatically used or you have to evolve the new plan for optimizer to use it.

So in a way SQL baselines are combination of outlines and profiles. It gives the stability of plan similar to outlines and it also allows capturing better plans in case the environment changes.

Not only that, SQL Baselines give the complete control to the DBA on

  1. Whether to capture the new and better plans?
  2. Whether to use the new plans automaticallly without DBA’s intervention.

So DBAs have complete control of the environment now. Far better than profiles and outlines.

SQL Plan Baseline Parameters:

Before we check how to use SQL Plan baselines, lets consider the significance of 2 important baseline parameters

1) optimizer_capture_sql_plan_baselines – Default “FALSE”

This parameter is responsible for automatic capturing the baselines for SQLs. If we set this parameter to TRUE we are asking oracle to automatically gather the baselines for the SQL.
When you run the query for the first time (and parsed version is not present in shared_pool), oracle consider that as a fresh new query and does not create a baseline.
When you run the query for second time, oracle will consider the query as repetative and will automatically create the baseline.
These baseline are stored in DBA_SQL_PLAN_BASELINES table.

If this parameter is set to FALSE, then we (DBA) has to create baselines for the SQL manually. There are 2 procedure available for creating baselines manually.

1) Using DBMS_SPM.LOAD_PLANS_FROM_SQLSET

This procedure is usually used when we create SQL tuning set and store our SQLs into the tuning sets. Normally done before we upgrade the database to 11g. In our 10g database we create SQL tuning set and store all our SQL. Once we upgrade to 11g, we can create baselines for all our plans in SQL tuning set. That why what ever plans were effective in 10g, same will be used in 11g and there wont be any plan flips.

2) Using DBMS_SPM.LOAD_PLANS_FROM_CURSOR_CACHE

This is used when we want to fix one of the SQL currently running in our 11g database. We just load the plans from cursor cache (shared_pool) and create baseline out of that. We need to give SQL ID as input to this procedure.

We will see how to create baseline using manual method at later point of time.

2) optimizer_use_sql_plan_baselines – Default “TRUE”

This parameter will allow optimizer to use the baselines present in DBA_SQL_PLAN_BASELINES table. If you set this parameter to FALSE, then your 11g DB will start behaving same as 10g DB.
If there is any change in environment then it might flip the plan. Keeping this parameter TRUE is important in 11g.

How to use SQL Plan Management baselines ?

Lets take an example in a test database.

Table T with 1540 records.

SQL> select count(1) from t;

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

Data is skewed and distribution is as given below.

SQL> select col1, count(1) from t group by col1;

      COL1   COUNT(1)
---------- ----------
         1          1
         2          3
         3       1536

Gather the stats on the table

SQL> exec dbms_stats.gather_table_stats(OWNNAME=> 'ADVAITD_DBA',TABNAME => 'T', DEGREE => 6, GRANULARITY => 'ALL' ,CASCADE => TRUE , METHOD_OPT => 'FOR ALL INDEXED COLUMNS SIZE 254');

SQL>

Currently I dont have any baseline.

SQL> select count(1) from dba_sql_plan_baselines;

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

SQL>

My baseline parameters setting is as below.

SQL> show parameters baselines

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
optimizer_capture_sql_plan_baselines boolean     FALSE
optimizer_use_sql_plan_baselines     boolean     TRUE
SQL>

Let us first consider the auto capture utility for baselines.

AUTO Capture of baseline

SQL> alter session set optimizer_capture_sql_plan_baselines=TRUE;

Session altered.

SQL>

As mention earlier, we need to run the query 2 times in order to automatically create the baseline.

SQL> select * from t where col1 = 1;

      COL1 COL2                                               COL3
---------- -------------------------------------------------- -----------
         1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 01-JUN-2011

SQL> select * from t where col1 = 1;

      COL1 COL2                                               COL3
---------- -------------------------------------------------- -----------
         1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 01-JUN-2011

If we check DBA_SQL_PLAN_BASELINES we will see a sql baseline created.

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED
  2  from dba_sql_plan_baselines
  3  WHERE sql_text like 'select * from t%';

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO

Turning off auto SQL plan baseline capture

SQL> alter session set optimizer_capture_sql_plan_baselines=FALSE;

Session altered.

SQL>

Following statement gives the plan stored in the baseline. DBMS_XPLAN has a new procedure DISPLAY_SQL_PLAN_BASELINE which will display the baseline.

SQL> set line 999
SQL> set pagesize 999
SQL> select * from table(dbms_xplan.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920f94ecae5c'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from t where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920f94ecae5c
Enabled: YES     Fixed: NO      Accepted: YES     Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 1601196873

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

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

   1 - filter("COL1"=1)

24 rows selected.

SQL>

Origin: AUTO-CAPTURE shown above tell us that this baseline is captured automatically.

Lets now create an index and gather stats over index.

SQL> create index t_idx on t(col1);

Index created.

SQL> exec DBMS_STATS.GATHER_INDEX_STATS(OWNNAME => 'ADVAITD_DBA', INDNAME=>'T_IDX');

Run the same query now, since the index is created, we expect the query to use the index.

SQL> explain plan for
  2  select * from t where col1 = 1;

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  |      |    15 |   735 |     9   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T    |    15 |   735 |     9   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920f94ecae5c" used for this statement

17 rows selected.

SQL>

The reason we are seeing full table scan is because of the NOTE at the end, which says “SQL plan baseline “SYS_SQL_PLAN_1d83920f94ecae5c” used for this statement”

Since we have a baseline created for this SQL, it will not allow the plan to be changed. This is the kind of stability that SQL Plan baseline gives.
But using an index will be beneficial in our case.

If we check DBA_SQL_PLAN_BASELINES we can see a new plan has been created (PLAN_NAME = SYS_SQL_PLAN_1d83920fae82cf72), but it is not yet ACCEPTED. The plan is enabled though.

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED
  2  from dba_sql_plan_baselines
  3  WHERE sql_text like 'select * from t%';

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES NO  NO

We can check what the new plan looks like using dbms_xplan.display_sql_plan_baseline

SQL> select * from table(dbms_xplan.display_sql_plan_baseline(plan_name=>'SYS_SQL_PLAN_1d83920fae82cf72'));

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

--------------------------------------------------------------------------------
SQL handle: SYS_SQL_1447ba3a1d83920f
SQL text: select * from t where col1 = 1
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
Plan name: SYS_SQL_PLAN_1d83920fae82cf72
Enabled: YES     Fixed: NO      Accepted: NO      Origin: AUTO-CAPTURE
--------------------------------------------------------------------------------

Plan hash value: 470836197

-------------------------------------------------------------------------------------
| Id  | Operation                   | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |       |    15 |   735 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |    15 |   735 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |     6 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

25 rows selected.

SQL>

As seen above, new plan uses index. Lets evolve this plan now.
Evolving a plan includes evaluating the cost of the plan and accepting if the plan seems to be better than all accepted plan for this query.

SQL> SELECT DBMS_SPM.evolve_sql_plan_baseline(sql_handle => 'SYS_SQL_1447ba3a1d83920f') from dual;

DBMS_SPM.EVOLVE_SQL_PLAN_BASELINE(SQL_HANDLE=>'SYS_SQL_1447BA3A1D83920F')
--------------------------------------------------------------------------------

-------------------------------------------------------------------------------
                        Evolve SQL Plan Baseline Report
-------------------------------------------------------------------------------

Inputs:
-------
  SQL_HANDLE = SYS_SQL_1447ba3a1d83920f
  PLAN_NAME  =
  TIME_LIMIT = DBMS_SPM.AUTO_LIMIT
  VERIFY     = YES
  COMMIT     = YES

Plan: SYS_SQL_PLAN_1d83920fae82cf72
-----------------------------------
  Plan was verified: Time used .01 seconds.
  Passed performance criterion: Compound improvement ratio >= 7.33
  Plan was changed to an accepted plan.

                      Baseline Plan      Test Plan     Improv. Ratio
                      -------------      ---------     -------------
  Execution Status:        COMPLETE       COMPLETE
  Rows Processed:                 1              1
  Elapsed Time(ms):               0              0
  CPU Time(ms):                   0              0
  Buffer Gets:                   22              3              7.33
  Disk Reads:                     0              0
  Direct Writes:                  0              0
  Fetches:                        0              0
  Executions:                     1              1

-------------------------------------------------------------------------------
                                 Report Summary
-------------------------------------------------------------------------------
Number of SQL plan baselines verified: 1.
Number of SQL plan baselines evolved: 1.

Sometimes, your plan may not get evolved because oracle see that there are other already ACCEPTED plans which are better than the plan you are trying to evolve.
But if you know your plan will be better and still want to deploy the same, you can do so by manually changing the attributes ACCEPTED and ENABLED as shown below.

SET SERVEROUTPUT ON
DECLARE
  l_plans_altered  PLS_INTEGER;
BEGIN
  l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
    sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
    plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',
    attribute_name  => 'ENABLED',
    attribute_value => 'YES');

  DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
END;
/

SET SERVEROUTPUT ON
DECLARE
  l_plans_altered  PLS_INTEGER;
BEGIN
  l_plans_altered := DBMS_SPM.alter_sql_plan_baseline(
    sql_handle      => 'SYS_SQL_1447ba3a1d83920f',
    plan_name       => 'SYS_SQL_PLAN_1d83920fae82cf72',
    attribute_name  => 'ACCEPTED',
    attribute_value => 'YES');

  DBMS_OUTPUT.put_line('Plans Altered: ' || l_plans_altered);
END;
/

You can also set the value of attribute FIXED using the above function. Here is the meaning of ENABLED, ACCPETED and FIXED

ENABLED   – ‘YES’ means the plan is available for use by the optimizer. It may or may not be used depending on accepted status.
ACCPETED – ‘YES’ means the plan will be used by optimizer while running the query. ‘NO’ means optimizer will not use the plan.
FIXED        – ‘YES’ means the SQL plan baseline is not evolved over time. A fixed plan takes precedence over a non-fixed plan.

Once you evolve the plan, you can see that plan is ACCEPTED now.

SQL> select SQL_HANDLE, PLAN_NAME, ENABLED, ACCEPTED, FIXED
  2  from dba_sql_plan_baselines
  3  WHERE sql_text like 'select * from t%';

SQL_HANDLE                     PLAN_NAME                      ENA ACC FIX
------------------------------ ------------------------------ --- --- ---
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920f94ecae5c  YES YES NO
SYS_SQL_1447ba3a1d83920f       SYS_SQL_PLAN_1d83920fae82cf72  YES YES NO

Now if you run the explain plan you can see Index T_IDX is getting used.

SQL> explain plan for
  2  select * from t where col1 = 1;

Explained.

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            |       |    15 |   735 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T     |    15 |   735 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IDX |     6 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------

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

   2 - access("COL1"=1)

Note
-----
   - SQL plan baseline "SYS_SQL_PLAN_1d83920fae82cf72" used for this statement

18 rows selected.

SQL>

Hope this helps.

Part 2 can be viewed at -http://avdeo.com/2011/06/07/oracle-sql-plan-management-%E2%80%93-part-2/

References:

http://download.oracle.com/docs/cd/B28359_01/server.111/b28274/optplanmgmt.htm

http://www.comp.dit.ie/btierney/oracle11gdoc/appdev.111/b28419/d_spm.htm

Creating Standby database using Active Duplication – Oracle Database 11g

Introduction:

Oracle database 11g introduced a new feature called Active database duplication. Using this feature you can create a new database (primary/standby) from your current running database. This feature does not needs any backup to be taken, nor we have to do any restore.

Creating active duplication is a RMAN feature and command for creating active duplication comes with various options.

Following is the command used for active duplication

duplicate target database to <DB_NAME> from active database;

Following are the various arguments that we can pass to this command

FROM ACTIVE DATABASE: (This is supplied if we want to do active database duplication)

Specifies that the files for the standby database should be provided directly from the source database and not from a backup of the source database

NOFILENAMECHECK:

Prevents RMAN from checking whether datafiles of the source database share the same names as the standby database files that are in use.

The NOFILENAMECHECK option is required when the standby and primary datafiles and online redo logs have identical filenames. Thus, if you want the duplicate database filenames to be the same as the source database filenames, and if the databases are in different hosts, then you must specify NOFILENAMECHECK

SPFILE:

Copies the server parameter file from the source database to the operating system-specific default location for this file on the standby database.

RMAN uses the server parameter file to start the auxiliary instance for standby database creation. Any remaining options of the DUPLICATE command are processed after the database instance is started with the server parameter file.

If you execute DUPLICATE with the SPFILE clause, then the auxiliary instance must already be started with a text-based initialization parameter file. In this case, the only required parameter in the temporary initialization parameter file is DB_NAME, which can be set to any arbitrary value. RMAN copies the binary server parameter file, modifies the parameters based on the settings in the SPFILE clause, and then restarts the standby instance with the server parameter file. When you specify SPFILE, RMAN never uses the temporary text-based initialization parameter file to start the instance.

If FROM ACTIVE DATABASE is specified on DUPLICATE, then a server parameter file must be in use by the source database instance. If FROM ACTIVE DATABASE is not specified on DUPLICATE, then RMAN restores a backup of the server parameter file to the standby database.

PARAMETER_VALUE_CONVERT:

Replaces the first string with the second string in all matching initialization parameter values. Note that DB_FILE_NAME_CONVERT and LOG_FILE_NAME_CONVERT are exceptions to this rule and are not affected.

You can use PARAMETER_VALUE_CONVERT to set a collection of initialization parameter values and avoid explicitly setting them all. For example, if the source database uses disk group +ALPHA while the standby database will use +BETA, then you could modify all parameters that refer to these disk groups by specifying SPFILE PARAMETER_VALUE_CONVERT (‘+ALHPA’,'+BETA’).

DORECOVER:

Specifies that RMAN should recover the standby database after creating it. If you specify an untilClause, then RMAN recovers to the specified SCN or time and leaves the database mounted.

RMAN leaves the standby database mounted after media recovery is complete, but does not place the standby database in manual or managed recovery mode. After RMAN creates the standby database, you must resolve any gap sequence before placing it in manual or managed recovery mode, or opening it in read-only mode.

For more details on arguments to be supplied check – http://download.oracle.com/docs/cd/B28359_01/backup.111/b28273/rcmsynta020.htm

Following is one example where I created a standby database using active database duplication.

Pre-requisite:

We should have a primary database up and running
pfile, spfile and listener.ora file should be available for both primary and standby
All the relevent directories for datafiles and diagnostic dest should be present
Password file for standby should be present
Entry should exist in /etc/oratab for both primary and standby

Steps for active database duplication

Step 1) nomount the standby database

sqlplus "/as sysdba"

SQL*Plus: Release 11.1.0.7.0 - Production on Wed May 4 06:53:47 2011

Copyright (c) 1982, 2008, Oracle.  All rights reserved.

Connected to an idle instance.

SQL> startup nomount
ORA-32004: obsolete and/or deprecated parameter(s) specified
ORACLE instance started.

Total System Global Area 2321612800 bytes
Fixed Size                  2174128 bytes
Variable Size            1198015312 bytes
Database Buffers         1073741824 bytes
Redo Buffers               47681536 bytes
SQL>


Step 2) Check the unique name for standby database

SQL> show parameters uniq

NAME                                 TYPE        VALUE
------------------------------------ ----------- ------------------------------
db_unique_name                       string      orcl_a

Step 3) Start active duplication

rman target sys/<password>@orcl1 auxiliary=sys/<password>@orcl1_a

Recovery Manager: Release 11.1.0.7.0 - Production on Wed May 4 07:01:22 2011

Copyright (c) 1982, 2007, Oracle.  All rights reserved.

connected to target database: ORCL1 (DBID=998984585)
connected to auxiliary database: ORCL1 (not mounted)

RMAN> duplicate target database to orcl1 from active database nofilenamecheck; --> This will create a new primary database again
RMAN> duplicate target database for standby from active  database nofilenamecheck; --> This will create a new standby database

Starting Duplicate Db at 04-MAY-11
allocated channel: ORA_AUX_DISK_1
channel ORA_AUX_DISK_1: SID=1122 device type=DISK

contents of Memory Script:
{
   backup as copy reuse
   file  '/opt/app/oracle/product/11.1.0.7/A24db/dbs/orapworcl1' auxiliary format
 '/opt/app/oracle/product/11.1.0.7/A24db/dbs/orapworcl1'   ;
}
executing Memory Script

Starting backup at 04-MAY-11
allocated channel: ORA_DISK_1
channel ORA_DISK_1: SID=1109 device type=DISK
Finished backup at 04-MAY-11

contents of Memory Script:
{
   backup as copy current controlfile for standby auxiliary format  '/ctl-01/databases/orcl1/control.ctl';
   restore clone controlfile to  '/ctl-02/databases/orcl1/control.ctl' from
 '/ctl-01/databases/orcl1/control.ctl';
   sql clone 'alter database mount standby database';
}
executing Memory Script

Starting backup at 04-MAY-11
using channel ORA_DISK_1
channel ORA_DISK_1: starting datafile copy
copying standby control file
output file name=/opt/app/oracle/product/11.1.0.7/A24db/dbs/snapcf_orcl1.f tag=TAG20110504T072433 RECID=2 STAMP=750237875
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:07
Finished backup at 04-MAY-11

Starting restore at 04-MAY-11
using channel ORA_AUX_DISK_1

channel ORA_AUX_DISK_1: copied control file copy
Finished restore at 04-MAY-11

sql statement: alter database mount standby database

contents of Memory Script:
{
   set newname for tempfile  1 to
 "/fs-b01-a/databases/orcl1/temp-01.dbf";
   set newname for tempfile  2 to
 "/fs-a01-a/databases/orcl1/dba_temp-01.dbf";
   set newname for tempfile  3 to
 "/fs-a01-a/databases/orcl1/temp-02.dbf";
   switch clone tempfile all;
   set newname for datafile  1 to
 "/fs-b01-a/databases/orcl1/system-01.dbf";
   set newname for datafile  2 to
 "/fs-a01-a/databases/orcl1/undo_t1-01.dbf";
   set newname for datafile  3 to
 "/fs-a01-a/databases/orcl1/sysaux-01.dbf";
   set newname for datafile  4 to
 "/fs-a01-a/databases/orcl1/administrator-01.dbf";
   set newname for datafile  5 to
 "/fs-b01-a/databases/orcl1/administrator_idx-01.dbf";
   set newname for datafile  6 to
 "/fs-a01-a/databases/orcl1/replication-01.dbf";
   set newname for datafile  7 to
 "/fs-b01-a/databases/orcl1/replication_idx-01.dbf";
   set newname for datafile  8 to
 "/fs-a01-a/databases/orcl1/rcvcat-001.dbf";
   set newname for datafile  9 to
 "/fs-b01-a/databases/orcl1/rcvcat_idx-001.dbf";
   set newname for datafile  10 to
 "/fs-d01-a/databases/orcl1/sysaux-20090310204613.dbf";
   set newname for datafile  11 to
 "/fs-d01-a/databases/orcl1/system-20090421171504.dbf";
   backup as copy reuse
   datafile  1 auxiliary format
 "/fs-b01-a/databases/orcl1/system-01.dbf"   datafile
 2 auxiliary format
 "/fs-a01-a/databases/orcl1/undo_t1-01.dbf"   datafile
 3 auxiliary format
 "/fs-a01-a/databases/orcl1/sysaux-01.dbf"   datafile
 4 auxiliary format
 "/fs-a01-a/databases/orcl1/administrator-01.dbf"   datafile
 5 auxiliary format
 "/fs-b01-a/databases/orcl1/administrator_idx-01.dbf"   datafile
 6 auxiliary format
 "/fs-a01-a/databases/orcl1/replication-01.dbf"   datafile
 7 auxiliary format
 "/fs-b01-a/databases/orcl1/replication_idx-01.dbf"   datafile
 8 auxiliary format
 "/fs-a01-a/databases/orcl1/rcvcat-001.dbf"   datafile
 9 auxiliary format
 "/fs-b01-a/databases/orcl1/rcvcat_idx-001.dbf"   datafile
 10 auxiliary format
 "/fs-d01-a/databases/orcl1/sysaux-20090310204613.dbf"   datafile
 11 auxiliary format
 "/fs-d01-a/databases/orcl1/system-20090421171504.dbf"   ;
   sql 'alter system archive log current';
}
executing Memory Script

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

renamed tempfile 1 to /fs-b01-a/databases/orcl1/temp-01.dbf in control file
renamed tempfile 2 to /fs-a01-a/databases/orcl1/dba_temp-01.dbf in control file
renamed tempfile 3 to /fs-a01-a/databases/orcl1/temp-02.dbf in control file

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

executing command: SET NEWNAME

Starting backup at 04-MAY-11
using channel ORA_DISK_1
channel ORA_DISK_1: starting datafile copy
input datafile file number=00008 name=/fs-a01-a/databases/orcl1/rcvcat-001.dbf
output file name=/fs-a01-a/databases/orcl1/rcvcat-001.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:02:05
channel ORA_DISK_1: starting datafile copy
input datafile file number=00009 name=/fs-b01-a/databases/orcl1/rcvcat_idx-001.dbf
output file name=/fs-b01-a/databases/orcl1/rcvcat_idx-001.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:02:05
channel ORA_DISK_1: starting datafile copy
input datafile file number=00010 name=/fs-d01-a/databases/orcl1/sysaux-20090310204613.dbf
output file name=/fs-d01-a/databases/orcl1/sysaux-20090310204613.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:01:15
channel ORA_DISK_1: starting datafile copy
input datafile file number=00011 name=/fs-d01-a/databases/orcl1/system-20090421171504.dbf
output file name=/fs-d01-a/databases/orcl1/system-20090421171504.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:01:05
channel ORA_DISK_1: starting datafile copy
input datafile file number=00002 name=/fs-a01-a/databases/orcl1/undo_t1-01.dbf
output file name=/fs-a01-a/databases/orcl1/undo_t1-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:25
channel ORA_DISK_1: starting datafile copy
input datafile file number=00004 name=/fs-a01-a/databases/orcl1/administrator-01.dbf
output file name=/fs-a01-a/databases/orcl1/administrator-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:25
channel ORA_DISK_1: starting datafile copy
input datafile file number=00005 name=/fs-b01-a/databases/orcl1/administrator_idx-01.dbf
output file name=/fs-b01-a/databases/orcl1/administrator_idx-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:25
channel ORA_DISK_1: starting datafile copy
input datafile file number=00006 name=/fs-a01-a/databases/orcl1/replication-01.dbf
output file name=/fs-a01-a/databases/orcl1/replication-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:25
channel ORA_DISK_1: starting datafile copy
input datafile file number=00007 name=/fs-b01-a/databases/orcl1/replication_idx-01.dbf
output file name=/fs-b01-a/databases/orcl1/replication_idx-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:25
channel ORA_DISK_1: starting datafile copy
input datafile file number=00001 name=/fs-b01-a/databases/orcl1/system-01.dbf
output file name=/fs-b01-a/databases/orcl1/system-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:15
channel ORA_DISK_1: starting datafile copy
input datafile file number=00003 name=/fs-a01-a/databases/orcl1/sysaux-01.dbf
output file name=/fs-a01-a/databases/orcl1/sysaux-01.dbf tag=TAG20110504T072447 RECID=0 STAMP=0
channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:15
Finished backup at 04-MAY-11

sql statement: alter system archive log current

contents of Memory Script:
{
   switch clone datafile all;
}
executing Memory Script

datafile 1 switched to datafile copy
input datafile copy RECID=2 STAMP=750238434 file name=/fs-b01-a/databases/orcl1/system-01.dbf
datafile 2 switched to datafile copy
input datafile copy RECID=3 STAMP=750238434 file name=/fs-a01-a/databases/orcl1/undo_t1-01.dbf
datafile 3 switched to datafile copy
input datafile copy RECID=4 STAMP=750238434 file name=/fs-a01-a/databases/orcl1/sysaux-01.dbf
datafile 4 switched to datafile copy
input datafile copy RECID=5 STAMP=750238434 file name=/fs-a01-a/databases/orcl1/administrator-01.dbf
datafile 5 switched to datafile copy
input datafile copy RECID=6 STAMP=750238434 file name=/fs-b01-a/databases/orcl1/administrator_idx-01.dbf
datafile 6 switched to datafile copy
input datafile copy RECID=7 STAMP=750238434 file name=/fs-a01-a/databases/orcl1/replication-01.dbf
datafile 7 switched to datafile copy
input datafile copy RECID=8 STAMP=750238434 file name=/fs-b01-a/databases/orcl1/replication_idx-01.dbf
datafile 8 switched to datafile copy
input datafile copy RECID=9 STAMP=750238434 file name=/fs-a01-a/databases/orcl1/rcvcat-001.dbf
datafile 9 switched to datafile copy
input datafile copy RECID=10 STAMP=750238434 file name=/fs-b01-a/databases/orcl1/rcvcat_idx-001.dbf
datafile 10 switched to datafile copy
input datafile copy RECID=11 STAMP=750238434 file name=/fs-d01-a/databases/orcl1/sysaux-20090310204613.dbf
datafile 11 switched to datafile copy
input datafile copy RECID=12 STAMP=750238434 file name=/fs-d01-a/databases/orcl1/system-20090421171504.dbf
Finished Duplicate Db at 04-MAY-11

RMAN>

This completes active duplication. Your standby is ready now.
Hope this helps !!

Avoiding “no data found” : Tips

Some time we face issue about no data found depending on selection criteria. And when this happens in PLSQL procedure we get annoying error

ORA-01403: No data found and execution stops.

One way to get around this error is to add exception block in our PLSQL procedure as shown below

EXCEPTION WHEN NO_DATA_FOUND THEN

<Take Action>

But disadvantage of using this approach is that it will not process rest of the code and control will jump to exception block and from there on continue till the end. If we get this error in the middle of FOR loop or WHILE loop, rest of the records will not get processed.

One of the way to deal with this is joining your SQL with dual.

In that case you are sure to get a null value even if the row does not exists.

Example in my case I want to find the memory value and spfile value of one of the parameter in database by querying v$parameter and v$spparameter view.

If I do a simple join as below I am going to get “no data found” if these view does not have parameter

SQL> select a.name, a.value, b.value from v$parameter b, v$spparameter a
  2  where a.name = b.name
  3  and a.name = 'shared_pool_reserved_min_alloc';

no rows selected

SQL>

But if we join with dual we can get the name of the parameter and a null value in front of that

SQL> select c.col, a.value, b.value
  2  from v$parameter b, v$spparameter a, (select 'shared_pool_reserved_min_alloc' col from dual) c
  3  where c.col = a.name (+) and c.col = b.name (+);

COL                            VALUE                VALUE
------------------------------ -------------------- --------------------
shared_pool_reserved_min_alloc

SQL>

This is easier to process.

Hope this helps !!

An insight into Oracle Index rebuild

You must have gone through my previous article on Oracle Index rebuild – 10g Vs 11g – http://avdeo.com/2011/03/17/oracle-index-rebuild-online-10g-vs-11g/

Well, there is another nice article written by a friend of mine Sumit Bhatia.

He has talked about the actions and events happening while rebuilding an index online in oracle database 11g.

Different wait events that we encounter and to make it more practical he has also build a script which gives you the exact status for your index rebuild.

You can refer to the article at http://fordba.wordpress.com/2011/04/05/online-ndx-rebuild/

You can refer to the script at http://fordba.wordpress.com/2011/03/30/ndx-progress-sql/

Just to mention, this script is by no means a complete script and enhancements can be underway. Also this script should not be used in production. If you want to use in production, you can do it at your own risk.

Hope this helps !!

Exchange Partition in Oracle

Some times we faced a need to convert our existing table from non-partition table to a partition table or vice-versa. Or moving a partition of one partition table to another partition. Or make one of the table as a partition of some partition table. Or make a partition of one of the partition table as a separate table.

To deal with this kind of situation, oracle has introduced exchange partition functionality long time a go (I guess from 8i onwards).
We will see a simple example of how to convert a simple non-partition table to a partition table.

1) Create a table, create index on table and gather stats on table and index


ORCL1>create table t as select * from dba_objects;

Table created.

ORCL1>create index t_idx on t(owner, object_name);

Index created.

ORCL1>exec dbms_stats.gather_table_stats('ADVAITD_DBA','T',cascade=>true);

PL/SQL procedure successfully completed.

ORCL1>select count(1) from t;

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

2) Create partition table with basic partition


ORCL1>create table pt (
 2  OWNER           VARCHAR2(30),
 3  OBJECT_NAME     VARCHAR2(128),
 4  SUBOBJECT_NAME  VARCHAR2(30),
 5  OBJECT_ID       NUMBER,
 6  DATA_OBJECT_ID  NUMBER,
 7  OBJECT_TYPE     VARCHAR2(19),
 8  CREATED         DATE,
 9  LAST_DDL_TIME   DATE,
 10  TIMESTAMP       VARCHAR2(19),
 11  STATUS          VARCHAR2(7),
 12  TEMPORARY       VARCHAR2(1),
 13  GENERATED       VARCHAR2(1),
 14  SECONDARY       VARCHAR2(1),
 15  NAMESPACE       NUMBER,
EDITION_NAME    VARCHAR2(30)
 16   17  )
 18  partition by range (CREATED)
 19  (PARTITION PT_2011 VALUES less than (MAXVALUE));

Table created.

ORCL1>select count(1) from pt;

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

3) Exchange the partition with table


ORCL1>alter table pt exchange partition PT_2011 with table T without validation;

Table altered.

ORCL1>select count(1) from pt;

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

ORCL1>select count(1) from t;

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

4) Split the partition and check

ALTER TABLE pt
SPLIT PARTITION PT_2011 AT (TO_DATE('31-DEC-2006', 'DD-MON-YYYY'))
INTO (PARTITION PT_2006,
 PARTITION PT_2011)

Analyze the table

exec dbms_stats.gather_table_stats('ADVAITD_DBA','PT',cascade=>true);

Check the number of rows in each partition


ORCL1>select TABLE_NAME, PARTITION_NAME, HIGH_VALUE, NUM_ROWS from dba_tab_partitions where TABLE_NAME = 'PT';

TABLE_NAME                     PARTITION_NAME                 HIGH_VALUE                       NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
PT                             PT_2006                        TO_DATE(' 2006-12-31 00:00:00'       8919
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2011                        MAXVALUE                             6047

Like that split again and check the records

ALTER TABLE pt
SPLIT PARTITION PT_2011 AT (TO_DATE('31-DEC-2007', 'DD-MON-YYYY'))
INTO (PARTITION PT_2007,
 PARTITION PT_2011)

exec dbms_stats.gather_table_stats('ADVAITD_DBA','PT',cascade=>true);

ORCL1>select TABLE_NAME, PARTITION_NAME, HIGH_VALUE, NUM_ROWS from dba_tab_partitions where TABLE_NAME = 'PT';

TABLE_NAME                     PARTITION_NAME                 HIGH_VALUE                       NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
PT                             PT_2006                        TO_DATE(' 2006-12-31 00:00:00'       8919
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2007                        TO_DATE(' 2007-12-31 00:00:00'        110
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2011                        MAXVALUE                             5937

Do for rest of the partitions


ALTER TABLE pt
SPLIT PARTITION PT_2011 AT (TO_DATE('31-DEC-2008', 'DD-MON-YYYY'))
INTO (PARTITION PT_2008,
 PARTITION PT_2011)

ALTER TABLE pt
SPLIT PARTITION PT_2011 AT (TO_DATE('31-DEC-2010', 'DD-MON-YYYY'))
INTO (PARTITION PT_2010,
 PARTITION PT_2011)

exec dbms_stats.gather_table_stats('ADVAITD_DBA','PT',cascade=>true);

ORCL1>select TABLE_NAME, PARTITION_NAME, HIGH_VALUE, NUM_ROWS from dba_tab_partitions where TABLE_NAME = 'PT';

TABLE_NAME                     PARTITION_NAME                 HIGH_VALUE                       NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------
PT                             PT_2006                        TO_DATE(' 2006-12-31 00:00:00'       8919
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2007                        TO_DATE(' 2007-12-31 00:00:00'        110
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2008                        TO_DATE(' 2008-12-31 00:00:00'        250
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

TABLE_NAME                     PARTITION_NAME                 HIGH_VALUE                       NUM_ROWS
------------------------------ ------------------------------ ------------------------------ ----------

PT                             PT_2009                        TO_DATE(' 2009-12-31 00:00:00'        312
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2010                        TO_DATE(' 2010-12-31 00:00:00'        163
 , 'SYYYY-MM-DD HH24:MI:SS', 'N
 LS_CALENDAR=GREGORIA

PT                             PT_2011                        MAXVALUE                             5212

6 rows selected.

Regarding the index, we can create the similar index on partition table as well. We will create it a local index

ORCL1>create index PT_IDX on PT(owner, object_name) local;
Index created.
ORCL1>

This step can be done before as well, but doesnt matter.

Now lets verify the results


ORCL1>select count(1) from dba_objects where created < to_date('2006-12-31','YYYY-MM-DD');

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

ORCL1>select count(1) from dba_objects where created < to_date('2007-12-31','YYYY-MM-DD') and created > to_date('2006-12-31','YYYY-MM-DD');

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

ORCL1>select count(1) from dba_objects where created < to_date('2008-12-31','YYYY-MM-DD') and created > to_date('2007-12-31','YYYY-MM-DD');

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

ORCL1>select count(1) from dba_objects where created < to_date('2009-12-31','YYYY-MM-DD') and created > to_date('2008-12-31','YYYY-MM-DD');

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

ORCL1>select count(1) from dba_objects where created < to_date('2010-12-31','YYYY-MM-DD') and created > to_date('2009-12-31','YYYY-MM-DD');

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

ORCL1>select count(1) from dba_objects where created < to_date('2011-12-31','YYYY-MM-DD') and created > to_date('2010-12-31','YYYY-MM-DD');

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

RCATLTN1>

So this is how we can convert a simple non-partition table to a partition table.
Don’t forgot to gather status again after creating all partitions and indexes.

Hope this helps !!

Virual Index and Invisible Index

Oracle has come up with a feature called virtual index in oracle 9i. This feature allow us to test an index on a table without actually adding an index on the table. The table will be visible only in our session and will be used by our queries only (if optimizer decide it to use). So basically the index will be visible to optimizer only in our sessions. Optimizer running query in other sessions won’t be able to see this index.

Virtual Index in Oracle 9i

Utility of using virtual index is that, suppose we have a table having huge number of rows and it is getting joined with other tables. If we see that optimizer is creating a plan which is costly and SQL tuning advisor suggest us to create an index on a column, then in case of production database we cannot simply create an index and test the changes. We need to make sure that creating that index wont have any negative impact on the execution plan of other queries running in this database.

So there is where we can use virtual index. Here is how virtual index works.

1) Creating a table

SQL> create table test as select * from dba_objects;

Table created.

2) Try selecting a value from test table

SQL> select * from test where object_name = 'STANDARD';

Execution Plan
----------------------------------------------------------
Plan hash value: 1357081020

----------------------------------
| Id  | Operation         | Name |
----------------------------------
|   0 | SELECT STATEMENT  |      |
|*  1 |  TABLE ACCESS FULL| TEST |
----------------------------------

3) Create a virtual Index on test table

SQL> create index test_idx_1 on test(object_name) nosegment;

Index created.

In order to create a virtual index, we need to give NOSEGMENT at the end of the create index statement. This will just create a index on the object_name column of test table. But it will not create an index segment in database.

You can check this by querying dba_objects and dba_indexes tables.

SQL> select index_name from dba_indexes where table_name = 'TEST' and index_name = 'TEST_IDX_1';

no rows selected
SQL> col OBJECT_NAME format a30;
SQL> select object_name, object_type from dba_objects where object_name = 'TEST_IDX_1';

OBJECT_NAME                    OBJECT_TYPE
------------------------------ -------------------
TEST_IDX_1                     INDEX

So, object exists in database, but we dont have segment for the same.

Now if you try to run the same select command on test table, still optimizer will NOT use virtual index.

SQL> select * from test where object_name = 'STANDARD';
Execution Plan
----------------------------------------------------------
Plan hash value: 1357081020

----------------------------------
| Id  | Operation         | Name |
----------------------------------
|   0 | SELECT STATEMENT  |      |
|*  1 |  TABLE ACCESS FULL| TEST |
----------------------------------

In order for optimizer to use virtual index, you need to set a parameter called _USE_NOSEGMENT_INDEXES in your session

SQL> alter session set "_USE_NOSEGMENT_INDEXES" = true;

Session altered.

Once you set this hidden parameter, optimizer will start using the virtual index you created on this table

SQL> select * from test where object_name = 'STANDARD';

Execution Plan
----------------------------------------------------------
Plan hash value: 1221747299

------------------------------------------------------------------------------------------
| Id  | Operation                   | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |            |     2 |   354 |     1   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| TEST       |     2 |   354 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | TEST_IDX_1 |    46 |       |     1   (0)| 00:00:01 |
------------------------------------------------------------------------------------------

If you run this query from some other session, it wont use virtual index.

you can analyze virtual indexes

SQL> analyze index TEST_IDX_1 compute statistics;

Index analyzed.

You CANNOT rebuild a virtual index

SQL> alter index TEST_IDX_1 rebuild;
alter index TEST_IDX_1 rebuild
*
ERROR at line 1:
ORA-08114: can not alter a fake index

Finally, if the index doesn’t seem to satisfy your requirement, then you can drop the virtual index.

SQL> drop index TEST_IDX_1;

Index dropped.

Invisible Index in 11g

We have a similar concept in Oracle database 11g called invisible index.

In case of invisible index, we can check if creating a new index is actually going to improve the performance or not. This index will not be visible to any other session and it will not be used by any other existing query run on same table.

SQL>drop table t;

Table dropped.

SQL>create table t as select * from dba_objects;

Table created.

SQL>create index t_ind1 on t(object_name) invisible;

Index created.

You can also make existing index as invisible using alter index command. You can make existing invisible index as visible. As soon as you make index visible, your existing queries will start using new index.

TDCLTN1>alter index t_ind1 visible;
TDCLTN1>alter index t_ind1 invisible;

We have a new column in USER_INDEXES called VISIBILITY. This tells whether an index is visible or not.

SQL>select index_name,VISIBILITY from user_indexes where index_name='T_IND1';

INDEX_NAME                     VISIBILIT
------------------------------ ---------
T_IND1                         INVISIBLE

So how does INVISIBLE index works ?

Now that we have created an INVISIBLE index, lets try to run a query on new table T and see if it uses the index.

SQL>explain plan for
 2  select * from t where object_name='STANDARD';

Explained.

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

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

----------------------------------
| Id  | Operation         | Name |
----------------------------------
|   0 | SELECT STATEMENT  |      |
|*  1 |  TABLE ACCESS FULL| T    |
----------------------------------

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

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

 1 - filter("OBJECT_NAME"='STANDARD')

So we can see its doing a full table scan.

In order to use the invisible index, we have a new parameter introduced in 11g – OPTIMIZER_USE_INVISIBLE_INDEXES

This parameter can be set to either TRUE or FALSE

If set to true, we are asking optimizer to use the invisible index if it can make a better plan using that.

Lets try to set this parameter to TRUE and run the same query.

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            |        |     2 |   202 |     4   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |     2 |   202 |     4   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | T_IND1 |     2 |       |     3   (0)| 00:00:01 |
-------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
1 - filter("OBJECT_NAME"='STANDARD')

Now it has used the index and the cost of the query also reduced.

If the index seems to make positive impact on the other query as well, then you can make this index as visible.

Note that if the index is INVISIBLE, then optimizer will ignore the index and 10053 trace will show index as “UNUSABLE”

Difference:

So based on above examples, we can see that the difference is

- In case of virtual index, we dont actually create the index, its just the place holder. In case of invisible index we actually create the index

- In case of virtual index, we can check if the index is getting used or not using explain plan and performance of the query can be mentioned statistically by explain plan. In case of invisible index, we can actually run the query and check the performance benefit.

- We cannot make existing index as virtual, unless we have to drop it and create a no_segment index. We can make any index invisible.

Hope this helps !!

Oracle Index rebuild online – 10g Vs 11g

An index is basically used for faster access to tables. Over a period of time the index gets fragmented because of several DMLs running on table.
When the index gets fragmented, data inside the index is scattered, rows / block reduces, index consumes more space and scanning of index takes more time and more blocks for same set of queries.
To talk in index terminology, we will have a single root block, but as fragmentation increases there will be more number of branch blocks and more leaf blocks. Also the height of index will increase.

To fix the above issue, we go for index rebuild. During index rebuild, the data inside the index is reorganized and compressed to fit in minimum number of blocks, height of the index is reduced to minimum possible level and performance of queries will increase.
Your search becomes faster and your query will read less number of blocks.

There are 2 methods to rebuild the index.

1) Offline index rebuild – alter index <index name> rebuild;
2) Online index rebuild  – alter index <index name> rebuild online;

With offline index rebuild, table and index is locked in exclusive mode preventing any translations on the table. This is most intrusive method and is used rarely in production unless we know for sure that modules are not going to access the table and we have complete downtime.

With online index rebuild, transactions can still access the table and index. Only for very less amount of time the lock is acquired on the table (by index rebuild operation). For rest of the time table and index is available for transactions.
However there is a difference in a way this internal online rebuild mechanism works in 10g and 11g. With 11g things are refined further to minimize the impact.

I will first explain the 10g method of rebuilding the index, after that will see the behavior in 11g

10g behaviour:

The way online index build (OIB) works is by creating an IOT journal table to keep track of changes while the OIB is in progress and merge all the changes from journal table to complete index build operation.

If I have a table with the structure of (A NUMBER, B NUMBER, C CHAR(100), D NUMBER) and to create index on (A, B, C) column, Oracle would create journal table like:

create table “ORACLE”.”SYS_JOURNAL_18155″ (C0 NUMBER, C1 NUMBER, C2 VARCHAR2(100), opcode char(1), partno number, rid rowid, primary key( C0, C1, C2 , rid )) organization index;

Before 11g, OIB will get in the DML queue to lock the table exclusively while preventing the new DML’s to go through, once all the active transactions (ones which were initiated before the OIB) are completed, OIB will create the journal IOT table and release the exclusive table lock (it’ll still keep the share lock on the table to prevent any other DDL’s) for DML’s to continue.

As part of journal table creation, Oracle would create an internal trigger on the primary table to record all the changes to the journal table. Along with using all the index columns, journal table will add “ROWID” to that list to make it as primary key.

“OPCODE” column represents the type of operation like “I” for Insert and “D” for Delete.
“PARTNO” column represents partition number of the underlying table.

Among all the changes to a given row for any of the index key columns, only the most recent change for that record is visible in the journal table. Any update operation of index key columns would be converted to “DELETE” and “INSERT” in the journal table.

While rest of the user sessions populate journal table with the ongoing changes for index key columns, OIB session would be reading the table in consistent mode (as of the time journal table is created) to create the index followed by the final merge operation.

During the merge process, Oracle will read the journal table (IOT) leaf blocks from left to right to merge those changes with the index being built. As the journal table leaf block changes are applied, once a leaf block is fully consumed, it’s reference will be deleted from the branch block.

This process will continue until all leaf blocks are consumed and when it comes to the last leaf block, Oracle would stop all the DML’s again to do the final merge and drop the journal table before releasing the mode 6 exclsuive table lock.

As each leaf block is consumed, Oracle would mark each entry as deleted. If more DML’s happen while Oracle is doing the merge, it’ll do one more pass of walking through the leaf blocks, this process continues until the merge process is all done.

If there is any long running DML before issuing the OIB, Oracle would wait on the long running transaction and it will prevent upcoming DML’s also. This is one of the major problems with OIB. Same thing can happen when it is doing the final merge.

So in the above process lock is acquired 2 times, one during the start of index creation when journal table is created online index rebuild process needs to be take exclusive lock on table to prevent DMLs from changing data. Once journal table is created online index rebuild process will release DML lock and hold a shared lock. Any DMLs happening after this will have entry made into journal table.
Again at the end of the process online index rebuild process will try to take exclusive lock to merge the last block of journal table into the main index.

Following example demonstrate the same:

Create test table and insert huge number of rows

</pre>
SQL>create table test as select * from dba_objects;

Table created.

SQL>insert into test select * from test;

29493 rows created.

SQL>/

58986 rows created.

Like this add more rows till it becomes big

Check the size of table


SQL>select sum(bytes)/1024/1024/1024 from dba_segments where segment_name = 'TEST';

SUM(BYTES)/1024/1024/1024
-------------------------
 .430053711

Create index on the table

SQL>create index I1 on test(OBJECT_NAME);

Index created.

Check the size of index.

SQL>select sum(bytes)/1024/1024/1024 from dba_segments where segment_name = 'I1';

SUM(BYTES)/1024/1024/1024
-------------------------
 .129272461

Now from the session 1, try inserting a record in the TEST table

Session 1 SID: 3204
SQL Text :

SQL>insert into test (owner, object_name) values ('AVDEO','NEW_TEST');
1 row created.
SQL>

From session 2, try rebuilding the index online

Session 2 SID: 3046
SQL text :

alter index I1 rebuild online

From session 3, run another DML command.

Session 3 SID: 3827
SQL Text:

update test set OWNER = ‘DEO2′ where OWNER = ‘DEO’

If we check v$lock table we can see session 2 (online index rebuild) is waiting on session 1 (insert). So unless insert completes, session 2 doing online index rebuild will not get a exclusive lock.


SQL>select
 2      l.SID oracle_id,
 3      decode(TYPE,
 4          'MR', 'Media Recovery',
 5          'RT', 'Redo Thread',
 6          'UN', 'User Name',
 7          'TX', 'Transaction',
 8          'TM', 'DML',
 9          'UL', 'PL/SQL User Lock',
 10          'DX', 'Distributed Xaction',
 11          'CF', 'Control File',
 12          'IS', 'Instance State',
 13          'FS', 'File Set',
 14          'IR', 'Instance Recovery',
 15          'ST', 'Disk Space Transaction',
 16          'TS', 'Temp Segment',
 17          'IV', 'Library Cache Invalidation',
 18          'LS', 'Log Start or Switch',
 19          'RW', 'Row Wait',
 20          'SQ', 'Sequence Number',
 21          'TE', 'Extend Table',
 22          'TT', 'Temp Table', type) lock_type,
 23      decode(LMODE,
 24          0, 'None',
 25          1, 'Null',
 26          2, 'Row-S (SS)',
 27          3, 'Row-X (SX)',
 28          4, 'Share',
 29          5, 'S/Row-X (SSX)',
 30          6, 'Exclusive', lmode) lock_held,
 31      decode(REQUEST,
 32          0, 'None',
 33          1, 'Null',
 34          2, 'Row-S (SS)',
 35          3, 'Row-X (SX)',
 36          4, 'Share',
 37          5, 'S/Row-X (SSX)',
 38          6, 'Exclusive', request) lock_requested,
 39      decode(BLOCK,
 40          0, 'Not Blocking',
 41          1, 'Blocking',
 42          2, 'Global', block) status,
 43      OBJECT_NAME
 44  from    v$locked_object lo,
 45      dba_objects do,
 46      v$lock l
 47  where     lo.OBJECT_ID = do.OBJECT_ID
 48  AND     l.SID = lo.SESSION_ID
 49  /

 ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 3204 DML                        Row-X (SX)                               None                                     Blocking             TEST
 3204 Transaction                Exclusive                                None                                     Not Blocking         TEST
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DML                        Row-S (SS)                               Share                                    Not Blocking         SYS_JOURNAL_1664589
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         TEST
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         TEST
 3075 DML                        Share                                    None                                     Not Blocking         TEST
 3075 DML                        Row-S (SS)                               Share                                    Not Blocking         TEST
 3900 DML                        None                                     Row-X (SX)                               Not Blocking         TEST

From the above output we can see that first session is 3204 which is running insert.
Session 2 is 3075 which is running index rebuild and is waiting for “DML share” lock
session 3 is 3900 running update DML and waiting for “DML Row-X” lock

If we see which session is blocking what we see below rows


Logn Ora    SQL/Prev                     OS                                                   Call
><    Sid-Ser-S Time User   Hash       Module            User   Svr-Pgm    Machine      HR Resource           Elap Ctim Locked Object
--------------- ---- ------ ---------- ----------------- ------ ---------- ------------ -- ------------------ ---- ---- --------------------
>  3204,23884-I 0953 ADVAIT 0          SQL*Plus          advait 7924-orac  db-fc-admin- 3  TM:1664558-0       448s 448s 1664558
 < 3075,20427-A 0959 ADVAIT 3645454058 SQL*Plus          advait 15432-orac db-fc-admin-  4                    433s 432s 1664558
 < 3900,30565-A 0959 ADVAIT 4227999514 SQL*Plus          advait 16004-orac db-fc-admin-  3                    408s 408s 1664558

Above output is generated by my custom scripts.

It says that 3204 is parent session and 3204 and 3900 is waiting on 3204.

In case of 10g if we commit session 1 (sid 3204 running insert), it will allow online index rebuild to continue and get the lock.
Session 3 will still continue to wait until session 2 running online index rebuild releases the lock.

After some time session 2 will start with index rebuild, it will take lock for very short period of time and releases the lock.
Session 3 (update DML) will aquire the lock after session 2 (index rebuild) releases the lock.

Session 3 completed while session 2 (index rebuild) does table scan.

SQL>update test set OWNER = 'DEO2' where OWNER = 'DEO'
 2  ;
1 rows updated.
SQL>

Now after session 3 DML completes, we didnt commit session 3. We are waiting for session 2 to complete now.

We can check v$session_longops to see current operation for index rebuild


SQL>select sid, OPNAME, SOFAR, TOTALWORK, TIME_REMAINING, ELAPSED_SECONDS from v$session_longops where sid = 3075;

 SID OPNAME                                                                SOFAR  TOTALWORK TIME_REMAINING ELAPSED_SECONDS
------ ---------------------------------------------------------------- ---------- ---------- -------------- ---------------
 3075 Sort Output                                                           25011      25011              0              15
 3075 Table Scan                                                            56036      56036              0              61

2 rows selected.

So we can see that v$session_longops that table scan for index rebuild completed. But still the session 2 for index rebuild is hanging.

If we run above query to see the locks it gives below output


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DML                        Row-S (SS)                               Share                                    Not Blocking         SYS_JOURNAL_1664589
 3075 Temp Segment               Exclusive                                None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 Transaction                Exclusive                                None                                     Not Blocking         SYS_JOURNAL_1664589
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         TEST
 3075 DL                         Row-X (SX)                               None                                     Not Blocking         TEST
 3075 DML                        Share                                    None                                     Not Blocking         TEST
 3075 DML                        Row-S (SS)                               Share                                    Not Blocking         TEST
 3075 Temp Segment               Exclusive                                None                                     Not Blocking         TEST
 3075 Transaction                Exclusive                                None                                     Not Blocking         TEST
 3900 DML                        Row-X (SX)                               None                                     Blocking             TEST

13 rows selected.

&nbsp;

So sid 3900 is the blocking session. This is session 3 which has run update query but havent commited.
So online index rebuild is waiting to acquire lock second time at the end. This is the time when it has to do the merging.

So after we commit session 3, session 2 doing online index rebuild will acquire the lock and will complete.

11g behaviour:

With 11g, significant changes were introduced to address all these problems.

Oracle will still wait for long running transactions to complete before starting the OIB process, but it will not block the upcoming DML’s. Oracle will not use exclusive table lock (mode 6) for OIB, which will eliminate any DML hang situations.

As soon as OIB process is initiated, Oracle will create IOT journal table and use internal trigger on the underlying table to keep track of the changes.

Once the journal table is created and the long running transactions are completed, Oracle will create the index by reading the table blocks in “CURRENT” mode, which means any committed changes in a block happened before the current SCN are read instead of the old way of accessing the blocks as of journal table creation time. This will virtually eliminate chance of running into rollback segment too small errors.

Oracle will recreate the internal trigger to directly modify the target index structure once the index build is completed (but the merge job is still pending). This means all user sessions will now directly update the target index instead of updating the journal table. Any change coming through would first be checked in the journal table before consolidating the change for the target index (this is best explanied with the example below).

Record “A” was inserted with rowid “RID” before the merge phase and hence it is tracked in the journal table.
Record “A” was deleted during the merge phase. Now the user session will read the journal table by record “A” with rowid “RID” and apply that change to the target index before deleteing the same record. It will also mark that record as consumed (i.e. deleted) in the IOT journal table leaf block.

While the user sessions continue to update the target index with the ongoing changes, OIB session will do the merge of journal table changes into the final index, as there will not be any ongoing DML changes being tracked in the journal table, this process will complete faster than before (i.e. pre 11g).

Lets take an example. In case of 11g we have MYTAB table and MYTAB_IDX index


SQL>select sum(bytes)/1024/1024 from dba_segments where segment_name = 'MYTAB';

SUM(BYTES)/1024/1024
--------------------
 406.5

SQL>select sum(bytes)/1024/1024 from dba_segments where segment_name = 'MYTAB_IDX';

SUM(BYTES)/1024/1024
--------------------
 152.5

In this case also do the following activity

From session 1 run a DML (insert statement) – SID 2213
From session 2 run index rebuild online command – SID 2222
From session 3 run a DML (update statement) – SID 2177

The current status in v$lock shows following


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 2177 AE                         Share                                    None                                     Not Blocking         MYTAB
 2177 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2177 Transaction                Exclusive                                None                                     Not Blocking         MYTAB
 2213 AE                         Share                                    None                                     Not Blocking         MYTAB
 2213 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2213 Transaction                Exclusive                                None                                     Blocking             MYTAB
 2222 AE                         Share                                    None                                     Not Blocking         MYTAB
 2222 OD                         Exclusive                                None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 OD                         Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                None                                     Share                                    Not Blocking         MYTAB
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         MYTAB
 2222 DML                        Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                Exclusive                                None                                     Not Blocking         MYTAB
 2222 AE                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                None                                     Share                                    Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701

Now if you compare it with 10g we see significant differences.

1) We see lot of extra locks in 11g compared to 10g
2) and the most important differences is that in case of 10g if you see last 2 rows in first output of v$lock we see


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 3075 DML                        Row-S (SS)                               Share                                    Not Blocking         TEST
 3900 DML                        None                                     Row-X (SX)                               Not Blocking         TEST

and same in 11g is


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 2177 AE                         Share                                    None                                     Not Blocking         MYTAB
 2177 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2177 Transaction                Exclusive                                None                                     Not Blocking         MYTAB
 2213 AE                         Share                                    None                                     Not Blocking         MYTAB
 2213 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2213 Transaction                Exclusive                                None                                     Blocking             MYTAB
 .
 .
 2222 Transaction                None                                     Share                                    Not Blocking         MYTAB

In case of 10g if you see 3075 is the one which is doing an index rebuild and is waiting on initial sid 3204. Also 3rd session (3900) running DML is waiting for “Row-X (SX)” lock.
In case of 11g if you see 2222 is the one which is doing an index rebuild and is waiting on initial sid 2213. Also 3rd session (2177) running DML already got “Row-X (SX)” lock and is not waiting for anything.

So in case of 11g if index rebuild is waiting for initial lock to acquire, it does not block incomming DMLs.

After I commit 1st session I see that none of the session is now blocking index rebuild operation and so it can acquire initial lock


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 2177 AE                         Share                                    None                                     Not Blocking         MYTAB
 2177 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2177 Transaction                Exclusive                                None                                     Not Blocking         MYTAB
 2222 AE                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 AE                         Share                                    None                                     Not Blocking         MYTAB
 2222 OD                         Exclusive                                None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 OD                         Share                                    None                                     Not Blocking         MYTAB
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         MYTAB
 2222 DML                        Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                Exclusive                                None                                     Not Blocking         MYTAB

My session 3 is already complete and I commited that session now. session 3 had no dependency on any session.

After index rebuild start (its doing table scan), I run one more DML from session 3, but I dont commit. In this case index rebuild will hang again at the end waiting for exclusive lock on table.
Unless session 3 gives the lock index rebuild cannot proceed.

Important change here between 10g and 11g is that if we start 4th session while index rebuild waits for 2nd time for lock and if I commit session 3, in case of 10g index rebuild will get precedence and it will acquire lock blocking session 4.
In case of 11g session 4 will get presedence and will acquire lock for DML, where as index rebuild will wait further until all DML sessions are complete and lock is available for it to acquire. So online index rebuild will prioritize all other sessions before him to acquire locks.

From v$session_longops we can see that tablescan operation completed


SQL>select sid, OPNAME, SOFAR, TOTALWORK, TIME_REMAINING, ELAPSED_SECONDS from v$session_longops where sid = 2222 and opname not like 'RMAN%';

 SID OPNAME                              SOFAR  TOTALWORK TIME_REMAINING ELAPSED_SECONDS
---------- ------------------------------ ---------- ---------- -------------- ---------------
 2222 Table Scan                          51736      51736              0              12
 2222 Sort Output                         21422      21422              0               7

Now online index rebuild session is waiting for session 3 (SID 2177), which I started which table scan operation was inprogress


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 2177 AE                         Share                                    None                                     Not Blocking         MYTAB
 2177 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2177 Transaction                Exclusive                                None                                     Blocking             MYTAB
 2222 AE                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                None                                     Share                                    Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 AE                         Share                                    None                                     Not Blocking         MYTAB
 2222 OD                         Exclusive                                None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 DL                         Row-X (SX)                               None                                     Not Blocking         MYTAB
 2222 OD                         Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                None                                     Share                                    Not Blocking         MYTAB
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         MYTAB
 2222 DML                        Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                Exclusive                                None                                     Not Blocking         MYTAB

Unless I commit that session index rebuild will wait. If I start another session now (session 4) and commit session 3, index rebuild will still wait for session 4 to complete.
Like this it can continue to wait until all transactions are done.

Example I started session 4 (SID 2223 ) and ran DML, commited in session 3

now index rebuild session (SID 2222) is waiting for session 4.


ORACLE_ID LOCK_TYPE                  LOCK_HELD                                LOCK_REQUESTED                           STATUS               OBJECT_NAME
---------- -------------------------- ---------------------------------------- ---------------------------------------- -------------------- ------------------------------
 2223 AE                         Share                                    None                                     Not Blocking         MYTAB
 2223 DML                        Row-X (SX)                               None                                     Not Blocking         MYTAB
 2223 Transaction                Exclusive                                None                                     Blocking             MYTAB
 2222 Transaction                None                                     Share                                    Not Blocking         SYS_JOURNAL_98701
 2222 AE                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 OD                         Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         SYS_JOURNAL_98701
 2222 DML                        Share                                    None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                Exclusive                                None                                     Not Blocking         SYS_JOURNAL_98701
 2222 Transaction                None                                     Share                                    Not Blocking         MYTAB
 2222 AE                         Share                                    None                                     Not Blocking         MYTAB
 2222 OD                         Exclusive                                None                                     Not Blocking         MYTAB
 2222 OD                         Share                                    None                                     Not Blocking         MYTAB
 2222 DML                        Row-S (SS)                               None                                     Not Blocking         MYTAB
 2222 DML                        Share                                    None                                     Not Blocking         MYTAB
 2222 Transaction                Exclusive                                None                                     Not Blocking         MYTAB

So main difference in case of online index rebuild procedure is online index rebuild process gives precedence to other DML sessions to acquire locks. The process has become less intrusive now.

Hope this helps !!

Follow

Get every new post delivered to your Inbox.

Join 125 other followers