1.Let us create a test case first:-
oracle@TST:TESTDB1[/home/oracle]$ sqlplus c##test/test
C##TEST@TESTDB1> create table mytst1 as select * from dba_objects;
Table created.
C##TEST@TESTDB1> create table mytst2 as select * from dba_objects where rownum<5000;
Table created.
2.Let us see what application executes SQL query and complaining poor performance
select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from mytst1 a,mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’;
3.Now create an index
In table mytst1,I can see column owner,object_name and object_id are used.So let me create index on those column.
Though We also see object_type used in query,I expect the following index should be good as We included object_id which is there in where clause(filter condition)
C##TEST@TESTDB1> create index mytest1_i1 on mytst1(OWNER,OBJECT_NAME,OBJECT_ID);
Index created.
I can see for the table mytst2,I can see column object_id and object_name are used in where clause(filter condition) .So let me create index on those column.
C##TEST@TESTDB1> create index mytest2_i1 on mytst2(OBJECT_ID,OBJECT_NAME);
Index created.
C##TEST@TESTDB1> exec dbms_stats.gather_schema_stats(‘C##TEST’);
Let me check execution plan of the query
C##TEST@TESTDB1> set autotrace on;
C##TEST@TESTDB1> select /*+ gather_plan_statistics */ a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from mytst1 a,mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
The execution plan shows full table scan.It seems index is not being picked up.
Execution Plan
———————————————————-
Plan hash value: 3196922646
—————————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 73 | 451 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 73 | 451 (1)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 25 (0)| 00:00:01 |
|* 3 | INDEX SKIP SCAN | MYTEST2_I1 | 1 | | 24 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | MYTST1 | 91062 | 4001K| 426 (1)| 00:00:01 |
—————————————————————————————————
Predicate Information (identified by operation id):
—————————————————
1 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
3 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
filter(“B”.”OBJECT_NAME”=’DBA_TABLES’)
6.Now let me re-look the indexes
I created earlier and found I had composite index with object_id last column in index.But this
is poor index and our filter condition either should be first column of index (not always true) or we need separate index exclusively on object_id column.
drop index mytest_i1 and create on object_id.The cost got improved.But this is not still best.It is just better.
C##TEST@TESTDB1> drop index mytest1_i1;
Index dropped.
C##TEST@TESTDB1> create index mytst1_i1 on mytst1(object_id);
Index created.
C##TEST@TESTDB1> select /*+ gather_plan_statistics */ a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from mytst1 a,mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
Execution Plan
———————————————————-
Plan hash value: 1970107425
—————————————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————-
| 0 | SELECT STATEMENT | | 1 | 73 | 27 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 73 | 27 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 73 | 27 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 25 (0)| 00:00:01 |
|* 4 | INDEX SKIP SCAN | MYTEST2_I1 | 1 | | 24 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MYTST1_I1 | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | MYTST1 | 1 | 45 | 2 (0)| 00:00:01 |
—————————————————————————————————-
Predicate Information (identified by operation id):
—————————————————
4 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
filter(“B”.”OBJECT_NAME”=’DBA_TABLES’)
5 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
7.Now creating exclusive index on object_id for mytst2 to avoid skip scan and drop old one.More cost improve far better.
C##TEST@TESTDB1> drop index MYTEST2_I1;
Index dropped.
C##TEST@TESTDB1> create index MYTEST2_I1 on MYTST2(OBJECT_NAME);
Index created.
C##TEST@TESTDB1> select /*+ gather_plan_statistics */ a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from mytst1 a,mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME =’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
Execution Plan
———————————————————-
Plan hash value: 929914574
—————————————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————-
| 0 | SELECT STATEMENT | | 1 | 73 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | MYTEST2_I1 | 1 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MYTST1_I1 | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | MYTST1 | 1 | 45 | 2 (0)| 00:00:01 |
—————————————————————————————————-
Predicate Information (identified by operation id):
—————————————————
4 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
5 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
8.Now revisit case 3 and I will show just changing the sequence of column in the index can improve plan drastically.
I will put object_id column first in composite index.
C##TEST@TESTDB1> drop index MYTST1_I1;
Index dropped.
C##TEST@TESTDB1> create index mytest1_i1 on mytst1(OBJECT_ID,OWNER,OBJECT_NAME);
Index created.
C##TEST@TESTDB1> select /*+ gather_plan_statistics */ a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from mytst1 a,mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME =’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
Execution Plan
———————————————————-
Plan hash value: 1963133166
—————————————————————————————————-
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————-
| 0 | SELECT STATEMENT | | 1 | 73 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | MYTEST2_I1 | 1 | | 1 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MYTEST1_I1 | 1 | | 1 (0)| 00:00:01 |
| 6 | TABLE ACCESS BY INDEX ROWID | MYTST1 | 1 | 45 | 2 (0)| 00:00:01 |
—————————————————————————————————-
Predicate Information (identified by operation id):
—————————————————
4 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
5 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
9.Let us see now if automatic sql tuning can provide us correct recommendation or not.
In my previous example I created some bad index first and Let sql tuning advisior identify it.
SYSTEM@TESTDB1> select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from c##test.mytst1 a,c##test.mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
SYSTEM@TESTDB1> SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format => ‘ADVANCED’)); 2
PLAN_TABLE_OUTPUT
———————————————————————————————————————————-
SQL_ID cpmdy46qwqckr, child number 0
————————————-
select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from c##test.mytst1
a,c##test.mytst2 b where a.OBJECT_ID=b.OBJECT_ID and
b.OBJECT_NAME=’DBA_TABLES’
Plan hash value: 3196922646
—————————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————
| 0 | SELECT STATEMENT | | | | 451 (100)| |
|* 1 | HASH JOIN | | 1 | 73 | 451 (1)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 25 (0)| 00:00:01 |
|* 3 | INDEX SKIP SCAN | MYTEST2_I1 | 1 | | 24 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | MYTST1 | 91062 | 4001K| 426 (1)| 00:00:01 |
—————————————————————————————————
Query Block Name / Object Alias (identified by operation id):
————————————————————-
1 – SEL$1
2 – SEL$1 / B@SEL$1
3 – SEL$1 / B@SEL$1
4 – SEL$1 / A@SEL$1
Outline Data
————-
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE(‘12.1.0.2’)
DB_VERSION(‘12.1.0.2′)
ALL_ROWS
OUTLINE_LEAF(@”SEL$1″)
INDEX_SS(@”SEL$1″ “B”@”SEL$1” (“MYTST2″.”OBJECT_ID” “MYTST2″.”OBJECT_NAME”))
BATCH_TABLE_ACCESS_BY_ROWID(@”SEL$1″ “B”@”SEL$1″)
FULL(@”SEL$1” “A”@”SEL$1″)
LEADING(@”SEL$1” “B”@”SEL$1” “A”@”SEL$1″)
USE_HASH(@”SEL$1” “A”@”SEL$1”)
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
—————————————————
1 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
3 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
filter(“B”.”OBJECT_NAME”=’DBA_TABLES’)
Column Projection Information (identified by operation id):
———————————————————–
1 – (#keys=1) “B”.”STATUS”[VARCHAR2,7], “A”.”OWNER”[VARCHAR2,128],
“A”.”OBJECT_NAME”[VARCHAR2,128], “A”.”OBJECT_TYPE”[VARCHAR2,23]
2 – “B”.”OBJECT_ID”[NUMBER,22], “B”.”STATUS”[VARCHAR2,7]
3 – “B”.ROWID[ROWID,10], “B”.”OBJECT_ID”[NUMBER,22]
4 – (rowset=200) “A”.”OWNER”[VARCHAR2,128], “A”.”OBJECT_NAME”[VARCHAR2,128],
“A”.”OBJECT_ID”[NUMBER,22], “A”.”OBJECT_TYPE”[VARCHAR2,23]
61 rows selected.
I will run tuning advisor now and tuning advisior correctly identified proper index to be created which I had identified from my experience
SYSTEM@TESTDB1> DECLARE
l_sql_tune_task_id VARCHAR2(100);
2 3 BEGIN
4 l_sql_tune_task_id := DBMS_SQLTUNE.create_tuning_task (
5 sql_id => ‘cpmdy46qwqckr’,
6 scope => DBMS_SQLTUNE.scope_comprehensive,
7 time_limit => 120,
8 task_name => ‘sql3_tuning_task_cpmdy46qwqckr’,
9 description => ‘Tuning task for statement cpmdy46qwqckr.’);
10 DBMS_OUTPUT.put_line(‘l_sql_tune_task_id: ‘ || l_sql_tune_task_id);
11 END;
12 /
PL/SQL procedure successfully completed.
SYSTEM@TESTDB1> EXEC DBMS_SQLTUNE.execute_tuning_task(task_name => ‘sql3_tuning_task_cpmdy46qwqckr’);
PL/SQL procedure successfully completed.
SYSTEM@TESTDB1> SET LINES 150
SET pages 50000
SET long 5000000
SET longc 5000000
SELECT DBMS_SQLTUNE.report_tuning_task(‘sql3_tuning_task_cpmdy46qwqckr’) AS recommendations FROM dual;
SYSTEM@TESTDB1> SYSTEM@TESTDB1> SYSTEM@TESTDB1> SYSTEM@TESTDB1> SYSTEM@TESTDB1>
RECOMMENDATIONS
——————————————————————————————————————————————————
GENERAL INFORMATION SECTION
——————————————————————————-
Tuning Task Name : sql3_tuning_task_cpmdy46qwqckr
Tuning Task Owner : SYSTEM
Workload Type : Single SQL Statement
Scope : COMPREHENSIVE
Time Limit(seconds): 120
Completion Status : COMPLETED
Started at : 01/18/2018 03:42:21
Completed at : 01/18/2018 03:42:23
——————————————————————————-
Schema Name : SYSTEM
Container Name: CDB$ROOT
SQL ID : cpmdy46qwqckr
SQL Text : select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from
c##test.mytst1 a,c##test.mytst2 b where
a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’
——————————————————————————-
FINDINGS SECTION (1 finding)
——————————————————————————-
1- Index Finding (see explain plans section below)
————————————————–
The execution plan of this statement can be improved by creating one or more
indices.
Recommendation (estimated benefit: 99.11%)
——————————————
– Consider running the Access Advisor to improve the physical schema design
or creating the recommended index.
create index C##TEST.IDX$$_00F20001 on
C##TEST.MYTST2(“OBJECT_NAME”,”OBJECT_ID”,”STATUS”);
– Consider running the Access Advisor to improve the physical schema design
or creating the recommended index.
create index C##TEST.IDX$$_00F20002 on C##TEST.MYTST1(“OBJECT_ID”);
Rationale
———
Creating the recommended indices significantly improves the execution plan
of this statement. However, it might be preferable to run “Access Advisor”
using a representative SQL workload as opposed to a single statement. This
will allow to get comprehensive index recommendations which takes into
account index maintenance overhead and additional space consumption.
——————————————————————————-
EXPLAIN PLANS SECTION
——————————————————————————-
1- Original
———–
Plan hash value: 3196922646
—————————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
—————————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 73 | 451 (1)| 00:00:01 |
|* 1 | HASH JOIN | | 1 | 73 | 451 (1)| 00:00:01 |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED| MYTST2 | 1 | 28 | 25 (0)| 00:00:01 |
|* 3 | INDEX SKIP SCAN | MYTEST2_I1 | 1 | | 24 (0)| 00:00:01 |
| 4 | TABLE ACCESS FULL | MYTST1 | 91062 | 4001K| 426 (1)| 00:00:01 |
—————————————————————————————————
Predicate Information (identified by operation id):
—————————————————
1 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
3 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
filter(“B”.”OBJECT_NAME”=’DBA_TABLES’)
2- Using New Indices
——————–
Plan hash value: 4293127463
———————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
———————————————————————————————–
| 0 | SELECT STATEMENT | | 1 | 73 | 4 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IDX$$_00F20001 | 1 | 28 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IDX$$_00F20002 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| MYTST1 | 1 | 45 | 2 (0)| 00:00:01 |
———————————————————————————————–
Predicate Information (identified by operation id):
—————————————————
3 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
4 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
——————————————————————————-
Let me create index suggested by tuning advisor and it indeed worked.
SYSTEM@TESTDB1> create index C##TEST.IDX$$_00F20001 on
C##TEST.MYTST2(“OBJECT_NAME”,”OBJECT_ID”,”STATUS”);
2
Index created.
SYSTEM@TESTDB1> create index C##TEST.IDX$$_00F20002 on C##TEST.MYTST1(“OBJECT_ID”);
Index created.
SYSTEM@TESTDB1> select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from c##test.mytst1 a,c##test.mytst2 b where a.OBJECT_ID=b.OBJECT_ID and b.OBJECT_NAME=’DBA_TABLES’;
OWNER
——————————————————————————————————————————–
OBJECT_NAME
——————————————————————————————————————————–
OBJECT_TYPE STATUS
———————– ——-
SYS
DBA_TABLES
VIEW VALID
PUBLIC
DBA_TABLES
SYNONYM VALID
Please check plan now.
SYSTEM@TESTDB1> SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(format => ‘ADVANCED’)); 2
PLAN_TABLE_OUTPUT
——————————————————————————————————————————————————
SQL_ID cpmdy46qwqckr, child number 0
————————————-
select a.owner,a.OBJECT_NAME,a.OBJECT_TYPE,b.STATUS from c##test.mytst1
a,c##test.mytst2 b where a.OBJECT_ID=b.OBJECT_ID and
b.OBJECT_NAME=’DBA_TABLES’
Plan hash value: 4293127463
———————————————————————————————–
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
———————————————————————————————–
| 0 | SELECT STATEMENT | | | | 4 (100)| |
| 1 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
| 2 | NESTED LOOPS | | 1 | 73 | 4 (0)| 00:00:01 |
|* 3 | INDEX RANGE SCAN | IDX$$_00F20001 | 1 | 28 | 2 (0)| 00:00:01 |
|* 4 | INDEX RANGE SCAN | IDX$$_00F20002 | 1 | | 1 (0)| 00:00:01 |
| 5 | TABLE ACCESS BY INDEX ROWID| MYTST1 | 1 | 45 | 2 (0)| 00:00:01 |
———————————————————————————————–
Query Block Name / Object Alias (identified by operation id):
————————————————————-
1 – SEL$1
3 – SEL$1 / B@SEL$1
4 – SEL$1 / A@SEL$1
5 – SEL$1 / A@SEL$1
Outline Data
————-
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE(‘12.1.0.2’)
DB_VERSION(‘12.1.0.2′)
ALL_ROWS
OUTLINE_LEAF(@”SEL$1″)
INDEX(@”SEL$1″ “B”@”SEL$1” (“MYTST2″.”OBJECT_NAME” “MYTST2″.”OBJECT_ID”
“MYTST2″.”STATUS”))
INDEX(@”SEL$1″ “A”@”SEL$1” (“MYTST1″.”OBJECT_ID”))
LEADING(@”SEL$1″ “B”@”SEL$1” “A”@”SEL$1″)
USE_NL(@”SEL$1” “A”@”SEL$1″)
NLJ_BATCHING(@”SEL$1” “A”@”SEL$1”)
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
—————————————————
3 – access(“B”.”OBJECT_NAME”=’DBA_TABLES’)
4 – access(“A”.”OBJECT_ID”=”B”.”OBJECT_ID”)
Column Projection Information (identified by operation id):
———————————————————–
1 – “B”.”OBJECT_ID”[NUMBER,22], “B”.”STATUS”[VARCHAR2,7],
“A”.”OWNER”[VARCHAR2,128], “A”.”OBJECT_NAME”[VARCHAR2,128],
“A”.”OBJECT_TYPE”[VARCHAR2,23]
2 – “B”.”OBJECT_ID”[NUMBER,22], “B”.”STATUS”[VARCHAR2,7], “A”.ROWID[ROWID,10]
3 – “B”.”OBJECT_ID”[NUMBER,22], “B”.”STATUS”[VARCHAR2,7]
4 – “A”.ROWID[ROWID,10]
5 – “A”.”OWNER”[VARCHAR2,128], “A”.”OBJECT_NAME”[VARCHAR2,128],
“A”.”OBJECT_TYPE”[VARCHAR2,23]
Note
—–
– this is an adaptive plan