Almost one year ago Iggy Fernandez asked me to write a short text for the Ask the Oracles column of the NoCOUG Journal. The topic was “Bind Variable Peeking: Bane or Boon?”. My text along with the ones of Wolfgang Breitling, Dan Tow and Jonathan Lewis were published in the August issue. For some (unknown) reasons I never published that text on this site. Today, I correct that oversight. The text can be downloaded from this page.
Monthly archives for April, 2010
Exadata Storage Server and the Query Optimizer – Part 1
Even though the utilization of an Exadata Storage Server should be transparent for the query optimizer, when you look at execution plans generated in an environment using it you might notice slight differences. The purpose of this series of post is to summarize the differences I was able to observe.
Disclaimer: I do not have access to an Exadata Storage Server (cell, from now on…). All tests I did, i.e. generation of execution plans, were performed with an 11gR2 database using “regular” storage. All I did is to set the CELL_OFFLOAD_PLAN_DISPLAY initialization parameter to ALWAYS. As a result, this post contains untested assumptions based on what I was able to observe in such an environment. It goes without saying that any correction is highly welcome!
Smart scan is one of the key performance features available only when a cell is used. Its purpose is to offload to the storage layer part of the work that would usually be performed by a database instance. As a result, not only the amount of work performed by a database instance might be (much) lower, but also the amount of data transferred from a cell to a database instance might be strongly reduced. When smart scan is used a cell, instead of sending back to the database engine “regular” database blocks (as a “regular” storage layer would do), sends back packets of data containing only the relevant information (I guess they are similar to the messages exchanged during PX operations).
Three are the basic smart scan techniques: projection, restriction and join filtering. As of Oracle Database 11g Release 2 three additional improvements are available: storage indexing, hybrid columnar compression and encryption. But, honestly, I consider them “simple” enhancements (that might have a major impact, though) of the previous ones. In other words, they add nothing to the basic concepts.
In this first post I cover projection. In the next posts I will cover the other two.
Whenever a query does not reference all columns belonging to the tables referenced in the FROM clause AND smart scan is used, it makes no sense that a cell sends back to a database instance the data belonging to the unreferenced columns. Since it makes no sense, it’s not done. Instead, a cell is able to extract only the data of the referenced columns. In this way, especially when queries reference few columns from wide tables, the amount of data received by a database instance is much smaller. As far as I know this technique is not explicitly externalized in execution plans (I mean, you don’t see which columns are actually sent back to the database instance). But, I guess, if you see one of the new operations added to support Exadata (they all contains the keyword “STORAGE” in them), you take advantage of this technique. Here is an example with a FTS (notice the keyword “STORAGE”):
------------------------------------------ | Id | Operation | Name | ------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS STORAGE FULL| T | ------------------------------------------
The full list of new “operations” that I was able to observe is the following:
- STORAGE FULL
- STORAGE SAMPLE
- STORAGE SAMPLE BY ROWID RANGE
- STORAGE BY ROWID RANGE
- STORAGE SAMPLE FAST FULL SCAN
- STORAGE FAST FULL SCAN
- STORAGE FULL SCAN
- STORAGE RANGE SCAN
- STORAGE FULL SCAN (MIN/MAX)
So, it seems that for the most common access paths there is a “smart scan version”.
Analyzing a SQL Trace File with SQL Statements
As of Oracle Database 11g the DBMS_SQLTUNE package provides the SELECT_SQL_TRACE function. Its purpose is to load the content of a SQL trace file into a SQL tuning set. But, as it often happens, a feature can be (mis)used for another purpose. The aim of this post is to show how to take advantage of this function to display through SQL statements the content of a SQL trace file.
Note that the SELECT_SQL_TRACE function is not available in version 11.1.0.6. Refer to the MOS note 790806.1 for additional information. Hence, the code shown in this post works as of 11.1.0.7 only.
First of all, let’s setup the scene.
- Create the necessary database objects and gather object statistics:
SQL> CREATE TABLE t
2 AS
3 SELECT rownum AS id, rpad('*',1000,'*') AS pad
4 FROM dual
5 CONNECT BY level <= 10000
6 ORDER BY dbms_random.value;
SQL> ALTER TABLE t ADD CONSTRAINT t_pk PRIMARY KEY (id);
SQL> BEGIN
2 dbms_stats.gather_table_stats(
3 ownname => user,
4 tabname => 't',
5 estimate_percent => 100,
6 method_opt => 'for all columns size 1'
7 );
8 END;
9 /
- Enable SQL trace (if you are asking yourself why I specify the PLAN_STAT parameter, have a look to this post):
SQL> execute dbms_monitor.session_trace_enable(binds => TRUE, plan_stat => 'ALL_EXECUTIONS')
- Run some queries:
SQL> EXECUTE :id := 10;
SQL> SELECT count(pad) FROM t WHERE id < :id;
COUNT(PAD)
----------
9
SQL> EXECUTE :id := 990;
SQL> SELECT count(pad) FROM t WHERE id < :id;
COUNT(PAD)
----------
989
SQL> SELECT count(pad) FROM t WHERE id < :id;
COUNT(PAD)
----------
989
SQL> EXECUTE :id := 20;
SQL> SELECT count(pad) FROM t WHERE id < :id;
COUNT(PAD)
----------
19
SQL> SELECT sum(id) FROM t;
SUM(ID)
----------
50005000
- Disable SQL trace and retrieve the name of the SQL trace file:
SQL> execute dbms_monitor.session_trace_disable SQL> SELECT value 2 FROM v$diag_info 3 WHERE name = 'Default Trace File'; VALUE ------------------------------------------------------------------- /u00/app/oracle/diag/rdbms/dba112/DBA112/trace/DBA112_ora_26790.trc
- Create a directory to read the SQL trace file through SQL statements
SQL> CREATE DIRECTORY trace AS '/u00/app/oracle/diag/rdbms/dba112/DBA112/trace/';
Now that we have a SQL trace file, let’s see how we can read its content with some simple queries and what kind of information we can extract.
- Retrieve a list of SQL statements executed by the current user (to exclude the recursive SQL statements executed by SYS) including their elapsed time and number of executions:
SQL> SELECT sql_id, 2 sum(elapsed_time) AS elapsed_time, 3 sum(executions) AS executions, 4 round(sum(elapsed_time)/sum(executions)) AS elapsed_time_per_execution 5 FROM table(dbms_sqltune.select_sql_trace( 6 directory => 'TRACE', 7 file_name => 'DBA112_ora_26790.trc', 8 select_mode => 2 -- all executions 9 )) t 10 WHERE parsing_schema_name = user 11 GROUP BY sql_id 12 ORDER BY elapsed_time DESC; SQL_ID ELAPSED_TIME EXECUTIONS ELAPSED_TIME_PER_EXECUTION ------------- ------------ ---------- -------------------------- asth1mx10aygn 249757 4 62439 6tgnxwpymddqc 4200 1 4200
- Retrieve the text of a particular SQL statement:
SQL> SELECT sql_text 2 FROM table(dbms_sqltune.select_sql_trace( 3 directory => 'TRACE', 4 file_name => 'DBA112_ora_26790.trc', 5 select_mode => 1 -- only first execution 6 )) t 7 WHERE sql_id = 'asth1mx10aygn'; SQL_TEXT --------------------------------------- SELECT count(pad) FROM t WHERE id < :id
- Retrieve more execution statistics about a particular SQL statement:
SQL> SELECT plan_hash_value, executions, fetches, elapsed_time, cpu_time, disk_reads, buffer_gets, rows_processed
2 FROM table(dbms_sqltune.select_sql_trace(
3 directory => 'TRACE',
4 file_name => 'DBA112_ora_26790.trc',
5 select_mode => 2 -- all executions
6 )) t
7 WHERE sql_id = 'asth1mx10aygn'
8 ORDER BY elapsed_time DESC;
PLAN_HASH_VALUE EXECUTIONS FETCHES ELAPSED_TIME CPU_TIME DISK_READS BUFFER_GETS ROWS_PROCESSED
--------------- ---------- ---------- ------------ ---------- ---------- ----------- --------------
4294967295 1 2 129056 127981 731 992 1
4294967295 1 2 113667 112982 691 1434 1
4294967295 1 2 5993 6999 11 11 1
4294967295 1 2 1041 1000 0 21 1
- Retrieve the value of the bind variables used to execute a particular SQL statement:
SQL> SELECT elapsed_time,
2 value(b).gettypename() AS type,
3 value(b).accessnumber() AS value
4 FROM table(dbms_sqltune.select_sql_trace(
5 directory => 'TRACE',
6 file_name => 'DBA112_ora_26790.trc',
7 select_mode => 2 -- all executions
8 )) t,
9 table(bind_list) b
10 WHERE sql_id = 'asth1mx10aygn'
11 ORDER BY elapsed_time DESC;
ELAPSED_TIME TYPE VALUE
------------ ---------- -----
129056 SYS.NUMBER 990
113667 SYS.NUMBER 990
5993 SYS.NUMBER 10
1041 SYS.NUMBER 20
Even though everything seems fine, there is an error in the output of one of the queries. Specifically, the hash value of the execution plan is not always the right one (the same value is used for all executions). In fact, because of extended cursor sharing (a.k.a. adaptive cursor sharing) several execution plans were used. This can be confirmed by analyzing the trace file with TKPROF (or another analyzer like TVD$XTAT). In this case the TKPROF output, generated with the AGGREGATE option set to "NO" provides the following information (notice that the hash value of the third execution is different):
... SQL ID: asth1mx10aygn Plan Hash: 4270555908 SELECT count(pad) FROM t WHERE id < :id ... SQL ID: asth1mx10aygn Plan Hash: 4270555908 SELECT count(pad) FROM t WHERE id < :id ... SQL ID: asth1mx10aygn Plan Hash: 2966233522 SELECT count(pad) FROM t WHERE id < :id ... SQL ID: asth1mx10aygn Plan Hash: 4270555908 SELECT count(pad) FROM t WHERE id < :id ...
The other information that can be retrieved through the SELECT_SQL_TRACE function is the execution plan. Unfortunately, extracting it directly through the function is inconvenient. The reason is that you should do the formatting yourself. Much easier is to load the information into a SQL tuning set and, then, to use the DBMS_XPLAN package to show its content. The following example illustrates this:
SQL> DECLARE
2 c sys_refcursor;
3 BEGIN
4 dbms_sqltune.create_sqlset('TEST');
5 OPEN c FOR
6 SELECT value(t)
7 FROM table(dbms_sqltune.select_sql_trace(
8 directory => 'TRACE',
9 file_name => 'DBA112_ora_26790.trc',
10 select_mode => 2 -- all executions
11 )) t;
12 dbms_sqltune.load_sqlset('TEST', c);
13 CLOSE c;
14 END;
15 /
SQL> SELECT *
2 FROM table(dbms_xplan.display_sqlset(
3 sqlset_name => 'TEST',
4 sql_id => 'asth1mx10aygn'
5 ));
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------
SQL Tuning Set Name: TEST
SQL Tuning Set Owner: CHA
SQL_ID: asth1mx10aygn
SQL Text: SELECT count(pad) FROM t WHERE id < :id
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
| Id | Operation | Name | Bytes | Cost |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | |
| 1 | SORT AGGREGATE | | | |
| 2 | TABLE ACCESS BY INDEX ROWID| UNKNOWN_OBJECT_90222 | 9045 | 11 |
| 3 | INDEX RANGE SCAN | UNKNOWN_OBJECT_90223 | | 2 |
-----------------------------------------------------------------------------
Notice that also in this case only one execution plan is shown.
All in all, this is an interesting feature. For sure it does not replace a profiler (mainly because the wait events are not shown), but it might be useful in some situations.