Introduction of Performance Tuning:
Performance tuning plays an important role in the DBA life. Performance tuning refers in the following areas:
1. Instance tuning
2. SQL tuning
3. Memory, Storage (I/O)
4. Network tuning
5. OS tuning
For OS Tuning and Network tuning DBAs are only a part of the tuning team but not 100 responsible. In these cases we would take help of server admins for OS level tuning and Network admin for network level.
DBAs are 100% responsible for INSTANCE, DATABASE, MEMORY, STORAGE level tuning
Tools used in Performance tuning:
1. Explain Plan of SQL Statements
2. Tracing
3. TK PROF
4. AUTO Trace – sqls executed only in the SQL Prompt
In this article we will discuss the use of the above tools one by one.
- Explain Plan of SQL Statement:
Explain plan is the method of identifying the execution plan which is generated by the optimizer whenever there is a new SQL running in the instance.
When you execute an SQL statement, it should pass through the optimizer or the explain plan which is already exist. If the execution plan is already exist then it will directly uses the execution plan to fetch the data in the database else it will create a new execution plan for the particular SQL statement when a explain plan command is issued so that next time you run the same statement, it can use the existing plan.
Syntax:
1 2 3 4 |
EXPLAIN PLAN {SET STATEMENT_ID = ‘statement_id’} {INTO table_name} FOR sql_statement |
Here in the syntax, statement_id and table_name are optional but if you want the explain plan for several statements to be stored in the table PALN_TABLE, you have to give a unique statement_id so that at the later point of time you can check the explain plans of the statements in the table PLAN_TABLE.
By default the explain plan of the statement will be stored in the PLAN_TABLE. You can also create your own PLAN_TABLE by using the script utlxplan.sql, which is in $ORACLE_HOME/rdbms/admin. From 10G oracle creates the table globally for all the users. As this table is global temporary table, we can see the plans inserted into the table by other sessions, and our plan will be disappeared once the session ends.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
[oracle@lexdty9036 ~]$ sqlplus / as sysdba SQL*Plus: Release 12.1.0.2.0 Production on Mon Jul 2 01:12:13 2018 Copyright (c) 1982, 2014, Oracle. All rights reserved. Connected to: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options SQL> desc PLAN_TABLE Name Null? Type ----------------------------------------- -------- ---------------------------- STATEMENT_ID VARCHAR2(30) PLAN_ID NUMBER TIMESTAMP DATE REMARKS VARCHAR2(4000) OPERATION VARCHAR2(30) OPTIONS VARCHAR2(255) OBJECT_NODE VARCHAR2(128) OBJECT_OWNER VARCHAR2(128) OBJECT_NAME VARCHAR2(128) OBJECT_ALIAS VARCHAR2(261) OBJECT_INSTANCE NUMBER(38) OBJECT_TYPE VARCHAR2(30) OPTIMIZER VARCHAR2(255) SEARCH_COLUMNS NUMBER ID NUMBER(38) PARENT_ID NUMBER(38) DEPTH NUMBER(38) POSITION NUMBER(38) COST NUMBER(38) CARDINALITY NUMBER(38) BYTES NUMBER(38) OTHER_TAG VARCHAR2(255) PARTITION_START VARCHAR2(255) PARTITION_STOP VARCHAR2(255) PARTITION_ID NUMBER(38) OTHER LONG OTHER_XML CLOB DISTRIBUTION VARCHAR2(30) CPU_COST NUMBER(38) IO_COST NUMBER(38) TEMP_SPACE NUMBER(38) ACCESS_PREDICATES VARCHAR2(4000) FILTER_PREDICATES VARCHAR2(4000) PROJECTION VARCHAR2(4000) TIME NUMBER(38) QBLOCK_NAME VARCHAR2(30) |
As I mentioned earlier that the PLAN_TABLE is accessible by all the users and the session, Let us see how is it possible.
1 2 3 4 5 6 7 8 9 10 |
SQL> select object_name,OBJECT_TYPE from dba_objects where object_name='PLAN_TABLE'; OBJECT_NAME OBJECT_TYPE -------------------- ----------------------- PLAN_TABLE SYNONYM From the above it is clear that the PLAN_TABLE is a SYNONYM. Now let us check the actual table name. SQL> select dbms_metadata.get_ddl('SYNONYM','PLAN_TABLE','PUBLIC') from dual; CREATE OR REPLACE NONEDITIONABLE PUBLIC SYNONYM "PLAN_TABLE" FOR "SYS"."PLAN_TABLE$” So PLAN_TABLE is a public synonym of SYS.PLAN_TABLE$ and thus it is accessible by all the users. |
Now let us create a tablespace and a user.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
SQL>create tablespace pttbs datafile ‘/app/oradata/pttbs01.dbf’ size 1G; SQL> create user ptuser identified by ptuser default tablespace PTTBS; SQL> ALTER USER PTUSER QUOTA UNLIMITED ON PTTBS; User altered. SQL> GRANT UNLIMITED TABLESPACE TO PTUSER; Grant succeeded. SQL> grant dba to PTUSER; Grant succeeded. SQL> commit ; Commit complete. SQL> conn PTUSER Enter password: Connected. SQL> insert into t1 values (&c1,'&c',&c3); Enter value for c1: 200 Enter value for c: A Enter value for c3: 1 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (200,'A',1) 1 row created. SQL> / Enter value for c1: 201 Enter value for c: B Enter value for c3: 2 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (201,'B',2) 1 row created. SQL> / Enter value for c1: 202 Enter value for c: C Enter value for c3: 3 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (202,'C',3) 1 row created. SQL> / Enter value for c1: 204 Enter value for c: D Enter value for c3: 4 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (204,'D',4) 1 row created. SQL> / Enter value for c1: 205 Enter value for c: E Enter value for c3: 4 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (205,'E',4) 1 row created. SQL> / Enter value for c1: 206 Enter value for c: F Enter value for c3: 5 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (206,'F',5) 1 row created. SQL> / Enter value for c1: 207 Enter value for c: G Enter value for c3: 5 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (207,'G',5) 1 row created. SQL> / Enter value for c1: 208 Enter value for c: H Enter value for c3: 6 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (208,'H',6) 1 row created. SQL> / Enter value for c1: 209 Enter value for c: I Enter value for c3: 7 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (209,'I',7) 1 row created. SQL> / Enter value for c1: 210 Enter value for c: J Enter value for c3: 8 old 1: insert into t1 values (&c1,'&c',&c3) new 1: insert into t1 values (210,'J',8) 1 row created. SQL> commit; Commit complete. SQL> select * from t1; C1 C2 C3 ---------- ----- ---------- 200 A 1 201 B 2 202 C 3 204 D 4 205 E 4 206 F 5 207 G 5 208 H 6 209 I 7 210 J 8 10 rows selected. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
SQL> desc plan_table; Name Null? Type ----------------------------------------- -------- ---------------------------- STATEMENT_ID VARCHAR2(30) PLAN_ID NUMBER TIMESTAMP DATE REMARKS VARCHAR2(4000) OPERATION VARCHAR2(30) OPTIONS VARCHAR2(255) OBJECT_NODE VARCHAR2(128) OBJECT_OWNER VARCHAR2(128) OBJECT_NAME VARCHAR2(128) OBJECT_ALIAS VARCHAR2(261) OBJECT_INSTANCE NUMBER(38) OBJECT_TYPE VARCHAR2(30) OPTIMIZER VARCHAR2(255) SEARCH_COLUMNS NUMBER ID NUMBER(38) PARENT_ID NUMBER(38) DEPTH NUMBER(38) POSITION NUMBER(38) COST NUMBER(38) CARDINALITY NUMBER(38) BYTES NUMBER(38) OTHER_TAG VARCHAR2(255) PARTITION_START VARCHAR2(255) PARTITION_STOP VARCHAR2(255) PARTITION_ID NUMBER(38) OTHER LONG OTHER_XML CLOB DISTRIBUTION VARCHAR2(30) CPU_COST NUMBER(38) IO_COST NUMBER(38) TEMP_SPACE NUMBER(38) ACCESS_PREDICATES VARCHAR2(4000) FILTER_PREDICATES VARCHAR2(4000) PROJECTION VARCHAR2(4000) TIME NUMBER(38) QBLOCK_NAME VARCHAR2(30) Here we can see that the newly created user also has the access to PLAN_TABLE. SQL> select count(*) from plan_table; COUNT(*) ---------- 0 Let us plan for the select statement as below. SQL> explain plan for select * from t1; Explained. SQL> select count(*) from plan_table; COUNT(*) ---------- 2 |
Now we have 2 methods of seeing the execution plan.
Method 1: Run select statement on top of the plan table and choose what are the columns for the output. But in this case there would be a lot of columns in a table and there are some mandatory columns as well without which we cannot map the output, hence it is difficult to choose the particular columns for the output.
SQL> select rtrim(lpad(‘ ‘ ,2*LEVEL) || rtrim(operation) || ‘ ‘|| rtrim(options) || ‘ ‘ || object_name) EXECUTION_PLAN, cost, cardinality from plan_table connect by prior id=parent_id start with id=0;
Method 2: Using the statement “select * from table(dbms_xplan.display());”
This is just put in the structured and readable format.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SQL> select * from table(dbms_xplan.display()); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Plan hash value: 3617692013 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 10 | 300 | 3 (0)| 00:00:01 | | 1 | TABLE ACCESS FULL | T1 | 10 | 300 | 3 (0)| 00:00:01 | -------------------------------------------------------------------------- Note ----- PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- - dynamic statistics used: dynamic sampling (level=2) 12 rows selected. |
Here the problem with the “dbms_xplan_plan” is that the execution plan is not the actual execution plan which includes the bind variables.
1 2 3 4 |
SQL> select sql_id,sql_text,child_number from v$sql where sql_text like 'select * from t1%'; SQL_ID SQL_TEXT CHILD_NUMBER --------------------------------------------------------------------------------------------------- 2z5kqzmsngdpd select * from t1 0 |
Now to see the actual execution plan for the statement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
SQL> SELECT * FROM TABLE (DBMS_XPLAN.display_cursor ('2z5kqzmsngdpd',0,'TYPICAL -BYTES')); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- SQL_ID 2z5kqzmsngdpd, child number 0 ------------------------------------- select * from t1 Plan hash value: 3617692013 ----------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Cost (%CPU) | Time ----------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | | 3 (100) | | | 1 | TABLE ACCESS FULL | T1 |10 | 3 (0) | 00:00:01| PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------- Note ----- - dynamic statistics used: dynamic sampling (level=2) 17 rows selected. |
The output is not from the not from PLAN_TABLE but from dynamic performance views (v$SQL_PLAN, V$SQL_PLAN_STATICS). And this output is same as previous in which we have executed using the dbms_xplan.display() and finding in PLAN_TABLE.
But the difference is in the PLAN_TABLE out put, the CPU utilised to execute the statement is ‘0’ which is not true as every statement which is executed will consume some cpu. Where as here in the actual output you can see the CPU utilised to execute the statement is 100 with the cost being same ‘3’.
Hence we have to user DBMS_XPLAN.display_cursor to get a better execution plan.
- Tracing SQL execution
Tracing is very useful to know which statement is problematic when a bunch of sql statements are being executed as part of a transaction.
There are two types of tracing methods.
1. End-to-End application tracing
2. SQL tracing.
End-to-End application tracing: Here application is nothing but the Database.
a. Tracing the Client identifier
b. Tracing Service, Module and Action
c. Tracing for session
d. Tracing for entire Database.
a. Tracing the Client identifier: DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE procedure enables event 10046 trace for all sessions with the specified client ID.
1 |
EXECUTE dbms_monitor.client_id_trace_enable (client_id=>'ID1',binds=>true,waits=>true); |
Here in this example ‘ID1’ is the client id that needs to be traced. Binds and waits are set to be TRUE which means both the bind variables and the wait events are included in the tracing. If we set to FALSE then they are out of tracing.
1 2 |
To disable the client identifier tracing: EXECUTE dbms_monitor.client_id_trace_disable (client_id=>'ID1'); |
b. Tracing for Service, Module and Action: SERV_MOD_ACT_TRACE_ENABLE Procedure enables the trace of service name, module, action for all the sessions unless an instance name is specified.
1 |
EXECUTE DBMS_MONITOR.SERV_MOD_ACT_TRACE_ENABLE(service_name => ‘sev1’, module_name => ‘RMS’, waits => FALSE, binds => FALSE, instance_name => ‘inst1’); |
SERV_MOD_ACT_TRACE_DISABLE Procedure disables the trace of service name, module, action for all the sessions
1 |
EXECUTE DBMS_MONITOR.SERV_MOD_ACT_TRACE_DISABLE(service_name => ‘sev1’, module_name => ‘RMS’, waits => FALSE, binds => FALSE, instance_name => ‘inst1’); |
c. Tracing at session level: If you want to enable the trace at the session level we require SID and SERIAL# of the particular session.We can get these by using the statement.
1 2 3 4 5 6 7 |
SQL> SELECT SID,SERIAL#,USERNAME FORM V$SESSION; Now to enable the tracing at session level below is the statement. SQL> EXECUTE DBMS_MONITOR.SESSION_TRACE ENABLE(session_id=>54,serial_num=>4323,waits=>TRUE,binds=>TRUE); To disable SQL> EXECUTE DBMS_MONITOR.SESSION_TRACE DISABLE(session_id=>54,serial_num=>4323); |
d. Tracing entire database: DATABASE_TRACE_ENABLE is the procedure used to trace the SQLs for complete database.
1 2 3 4 5 6 7 8 9 10 11 |
EXECUTE DBMS_MONITOR.DATABASE_TRACE ENABLE(waits=>TRUE,binds=>TRUE, instance_name=>’inst1’); To Disable the tracing of the database EXECUTE DBMS_MONITOR.DATABASE_TRACE DISABLE(); All the trace files that are generated using the above methods are generated in the user_dump destination. SQL> show parameter user_dump; NAME TYPE VALUE --------------------------------------------------------------------------------------- user_dump_dest string /app/oracle/product/12.1.0/dbhome_1/rdbms/trace |
Here as all the trace files are placed in one location user_dump_dest, it is a bit difficult to find the trace file that you have generated as by default the naming convention would be “Dbname_ora_osprocesid.trc”. So we can use the tracefile identifier to identify the trace file.
1 2 |
SQL> ALTER SESSION SET tracefile_identifier=sessiontrace; SQL> ALTER SESSION SET SQL_TRACE=TRUE; |
Now this will enable the trace of our own session and the trace file will have the extension “sessiontrace” so that we can easily identify the trace file.
1 |
Select s.sql_trace,s.sql_trace_waits, s.sql._trace_binds, trace_id, tracefile from v$session s join v$process p on (p.addr = s.paddr) where audsid = userenv (‘sessionid’); |
By using the above statement we can check if the tracing is enabled or not.
3. TKPROF: TKPROF is used to analyse the trace which is generated.
Below are the tkprof options that we can use
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
[oracle@sum9036 ~]$ tkprof Usage: tkprof tracefile outputfile [explain= ] [table= ] [print= ] [insert= ] [sys= ] [sort= ] table=schema.tablename Use 'schema.tablename' with 'explain=' option. explain=user/password Connect to ORACLE and issue EXPLAIN PLAN. print=integer List only the first 'integer' SQL statements. aggregate=yes|no insert=filename List SQL statements and data inside INSERT statements. sys=no TKPROF does not list SQL statements run as user SYS. record=filename Record non-recursive statements found in the trace file. waits=yes|no Record summary for any wait events found in the trace file. sort=option Set of zero or more of the following sort options: prscnt number of times parse was called prscpu cpu time parsing prsela elapsed time parsing prsdsk number of disk reads during parse prsqry number of buffers for consistent read during parse prscu number of buffers for current read during parse prsmis number of misses in library cache during parse execnt number of execute was called execpu cpu time spent executing exeela elapsed time executing exedsk number of disk reads during execute exeqry number of buffers for consistent read during execute execu number of buffers for current read during execute exerow number of rows processed during execute exemis number of library cache misses during execute fchcnt number of times fetch was called fchcpu cpu time spent fetching fchela elapsed time fetching fchdsk number of disk reads during fetch fchqry number of buffers for consistent read during fetch fchcu number of buffers for current read during fetch fchrow number of rows fetched userid userid of user that parsed the cursor Example of generating the TKPROF from the Trace. sum9036]$tkprof repdb_ora_32262.trc repdb_ora_32262.out |
1 2 |
tkprof repdb_ora_32262.trc repdb_ora_32262_explained.out explain=user/password T</code>his will produce an EXPLAIN PLAN next to the SQL we are after directly in the output file. |
4. Autotrace:
Using the Autotrace we can see the out of the statement suppressed with the Execution Plan and the execution Statistics. This is used only when the queries are executed in the sql prompt.
1 2 3 4 5 6 7 8 9 10 |
SQL> set autotrace on; SQL> select * from t1; Options: 1. SQL> set autotrace traceonly: This will not show the ouput of the statement suppressed with the number of rows, actual expalin plan and the execution statistics. 2. SQL> set autotrace on explain This will show the output of the sql statement suppressed with the explain plan only. 3. SQL> set autotrace on statistice This will show the ouput of the sql statement suppressed with the execution statistics only |
kumar
Nice article suman.
SUMAN MORA
Thank you
amin
Nice doc for learning purpose .