<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Striving for Optimal Performance &#187; 10gR2</title>
	<atom:link href="http://antognini.ch/category/oracledatabase/10gr2/feed/" rel="self" type="application/rss+xml" />
	<link>http://antognini.ch</link>
	<description></description>
	<lastBuildDate>Sat, 17 Dec 2011 23:42:45 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>SQL Trace and Oracle Portal</title>
		<link>http://antognini.ch/2011/11/sql-trace-and-oracle-portal/</link>
		<comments>http://antognini.ch/2011/11/sql-trace-and-oracle-portal/#comments</comments>
		<pubDate>Tue, 29 Nov 2011 10:32:34 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[SQL Trace]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1637</guid>
		<description><![CDATA[Recently I was involved in a project where I had to trace the database calls of an application based on Oracle Portal 10.1.4. The basic requirements were the following:

Tracing takes place in the production environment
Tracing has to be enable for a single user only
Instrumentation code cannot be added to the application

Given that Oracle Portal uses [...]]]></description>
			<content:encoded><![CDATA[<p>Recently I was involved in a project where I had to trace the database calls of an application based on Oracle Portal 10.1.4. The basic requirements were the following:</p>
<ul>
<li>Tracing takes place in the production environment</li>
<li>Tracing has to be enable for a single user only</li>
<li>Instrumentation code cannot be added to the application</li>
</ul>
<p>Given that Oracle Portal uses a pool of connections and that for each HTTP call it can use several database sessions, statically enable SQL trace for specific sessions was not an option.</p>
<p>Knowing nothing about Oracle Portal I started RTFM and, gladly, I discovered that there is a simple way to inject a piece of code before and after a requested procedure is called. This is done by setting, via the administration GUI, the parameters <a href="http://docs.oracle.com/cd/B14099_19/web.1012/b14008/confmods.htm#sthref632">PlsqlBeforeProcedure</a> and <a href="http://docs.oracle.com/cd/B14099_19/web.1012/b14008/confmods.htm#sthref625">PlsqlAfterProcedure</a>. </p>
<p>Since Oracle Portal provides a function (<a href="http://docs.oracle.com/cd/B14099_19/portal.1012/b14134/pdg_pdk_plsql.htm#sthref1442">WWCTX_API.GET_USER</a>) to get the current user, I decided to create the following procedures to set/clear the client identifier before/after every call. Note that I added the call to SUBSTR and the exception handler to make sure that the procedures do not raise exceptions (hey, it’s a production system and I do not want to impact everyone!).</p>
<p><pre>CREATE PROCEDURE tvd_set_client_identifier AS
BEGIN
  dbms_session.set_identifier(substr(portal.wwctx_api.get_user,1,64));
EXCEPTION
  WHEN others THEN NULL;
END;</pre></p>
<p><pre>CREATE PROCEDURE tvd_clear_client_identifier AS
BEGIN
  dbms_session.clear_identifier;
EXCEPTION
  WHEN others THEN NULL;
END;</pre></p>
<p>To &#8220;enable&#8221; these procedures we did the following:</p>
<ul>
<li>Set PlsqlBeforeProcedure=portal.tvd_set_client_identifier</li>
<li>Set PlsqlAfterProcedure=portal.tvd_clear_client_identifier</li>
<li>Restarted the application server</li>
</ul>
<p>With this configuration in place enabling SQL trace for a single user was easily done by calling the <a href="http://docs.oracle.com/cd/B19306_01/appdev.102/b14258/d_monitor.htm#i1002256">DBMS_MONITOR.CLIENT_ID_TRACE_ENABLE</a> procedure by specifying the user to be traced as value for the CLIENT_ID parameter.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/11/sql-trace-and-oracle-portal/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>optimizer_secure_view_merging and VPD</title>
		<link>http://antognini.ch/2011/09/optimizer_secure_view_merging-and-vpd/</link>
		<comments>http://antognini.ch/2011/09/optimizer_secure_view_merging-and-vpd/#comments</comments>
		<pubDate>Sun, 11 Sep 2011 09:21:49 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Query Optimizer]]></category>
		<category><![CDATA[TOP]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1552</guid>
		<description><![CDATA[At page 189 of TOP I wrote the following piece of text:
In summary, with the initialization parameter optimizer_secure_view_merging set to TRUE, the query optimizer checks whether view merging could lead to security issues. If this is the case, no view merging will be performed, and performance could be suboptimal as a result. For this reason, [...]]]></description>
			<content:encoded><![CDATA[<p>At page 189 of <a href="/top">TOP</a> I wrote the following piece of text:</p>
<blockquote><p>In summary, with the initialization parameter optimizer_secure_view_merging set to TRUE, the query optimizer checks whether view merging could lead to security issues. If this is the case, no view merging will be performed, and performance could be suboptimal as a result. For this reason, if you are not using views for security purposes, it is better to set this initialization parameter to FALSE.</p></blockquote>
<p>What I didn’t consider when I wrote it, it is the implication of predicate move-around related to Virtual Private Database (VPD). In fact, as described in the <a href="http://download.oracle.com/docs/cd/E11882_01/server.112/e17110/initparams168.htm#I1010262">documentation</a>, that parameter controls <em>view merging</em> as well as <em>predicate move-around</em>. </p>
<p>To point out what the impact is, let’s have a look to an example based on the description provided in <a href="/top">TOP</a>:</p>
<ul>
<li>Say you have a very simple table with one primary key and two more columns.</li>
</ul>
<p><pre>CREATE TABLE t (
  id NUMBER(10) PRIMARY KEY,
  class NUMBER(10),
  pad VARCHAR2(10)
);</pre></p>
<ul>
<li>For security reasons, you define the following policy. Notice the filter that is applied with the function to partially show the content of the table. How this function is implemented and what it does exactly is not important.</li>
</ul>
<p><pre>CREATE OR REPLACE FUNCTION s (schema IN VARCHAR2, tab IN VARCHAR2) RETURN VARCHAR2 AS
BEGIN
  RETURN &#039;f(class) = 1&#039;;
END;
/

BEGIN
  dbms_rls.add_policy(object_schema   =&gt; &#039;U1&#039;,
                      object_name     =&gt; &#039;T&#039;,
                      policy_name     =&gt; &#039;T_SEC&#039;,
                      function_schema =&gt; &#039;U1&#039;,
                      policy_function =&gt; &#039;S&#039;);
END;
/</pre></p>
<ul>
<li>Now let’s say that a user who has access to the table creates the following PL/SQL function. As you can see, it will just display the value of the input parameters through a call to the package dbms_output.</li>
</ul>
<p><pre>CREATE OR REPLACE FUNCTION spy (id IN NUMBER, pad IN VARCHAR2) RETURN NUMBER AS
BEGIN
  dbms_output.put_line(&#039;id=&#039; || id || &#039; pad=&#039; || pad);
  RETURN 1;
END;
/</pre></p>
<ul>
<li>With the initialization parameter optimizer_secure_view_merging set to FALSE, you can run two test queries. Both return only the values that the user is allowed to see. In the second one, however, you are able to see data that you should not be able to access.</li>
</ul>
<p><pre>SQL&gt; SELECT id, pad
  2  FROM t
  3  WHERE id BETWEEN 1 AND 5;

        ID PAD
---------- ----------
         1 DrMLTDXxxq
         4 AszBGEUGEL

SQL&gt; SELECT id, pad
  2  FROM t
  3  WHERE id BETWEEN 1 AND 5
  4  AND spy(id, pad) = 1;

        ID PAD
---------- ----------
         1 DrMLTDXxxq
         4 AszBGEUGEL
id=1 pad=DrMLTDXxxq
id=2 pad=XOZnqYRJwI
id=3 pad=nlGfGBTxNk
id=4 pad=AszBGEUGEL
id=5 pad=qTSRnFjRGb</pre></p>
<ul>
<li>With the initialization parameter optimizer_secure_view_merging set to TRUE, the second query returns the following output. As you can see, the function and the query display the same data.</li>
</ul>
<p><pre>SQL&gt; SELECT id, pad
  2  FROM t
  3  WHERE id BETWEEN 1 AND 5
  4  AND spy(id, pad) = 1;

        ID PAD
---------- ----------
         1 DrMLTDXxxq
         4 AszBGEUGEL
id=1 pad=DrMLTDXxxq
id=4 pad=AszBGEUGEL</pre></p>
<p>The execution plans that are used in the two situations are the following. As you can see only the second one guarantee that the policy defined via VPD is applied before the predicate based on the SPY function. Interestingly enough the other predicate based on the ID column is applied before the one of the policy. Hence, the query optimizer can choose an access path that takes advantage of the primary key.</p>
<ul>
<li>optimizer_secure_view_merging = FALSE</li>
</ul>
<p><pre>---------------------------------------------------
| Id  | Operation                   | Name        |
---------------------------------------------------
|   0 | SELECT STATEMENT            |             |
|*  1 |  TABLE ACCESS BY INDEX ROWID| T           |
|*  2 |   INDEX RANGE SCAN          | SYS_C009970 |
---------------------------------------------------

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

   1 - filter((&quot;SPY&quot;(&quot;ID&quot;,&quot;PAD&quot;)=1 AND &quot;F&quot;(&quot;CLASS&quot;)=1))
   2 - access(&quot;ID&quot;&gt;=1 AND &quot;ID&quot;&lt;=5)</pre></p>
<ul>
<li>optimizer_secure_view_merging = TRUE</li>
</ul>
<p><pre>----------------------------------------------------
| Id  | Operation                    | Name        |
----------------------------------------------------
|   0 | SELECT STATEMENT             |             |
|*  1 |  VIEW                        | T           |
|*  2 |   TABLE ACCESS BY INDEX ROWID| T           |
|*  3 |    INDEX RANGE SCAN          | SYS_C009971 |
----------------------------------------------------

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

   1 - filter(&quot;SPY&quot;(&quot;ID&quot;,&quot;PAD&quot;)=1)
   2 - filter(&quot;F&quot;(&quot;CLASS&quot;)=1)
   3 - access(&quot;ID&quot;&gt;=1 AND &quot;ID&quot;&lt;=5)</pre></p>
<p>Based on these observations, the summary that is provided by <a href="/top">TOP</a> at page 189 should be amended as follows:</p>
<blockquote><p>In summary, with the initialization parameter optimizer_secure_view_merging set to TRUE, the query optimizer checks whether view merging or predicate move-around could lead to security issues. If this is the case, they will not be performed, and performance could be suboptimal as a result. For this reason, if you are not using views or VPD for security purposes, it is better to set this initialization parameter to FALSE.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/09/optimizer_secure_view_merging-and-vpd/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>ITL Waits – Changes in Recent Releases (script)</title>
		<link>http://antognini.ch/2011/06/itl-waits-%e2%80%93-changes-in-recent-releases-script/</link>
		<comments>http://antognini.ch/2011/06/itl-waits-%e2%80%93-changes-in-recent-releases-script/#comments</comments>
		<pubDate>Mon, 20 Jun 2011 10:20:02 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1533</guid>
		<description><![CDATA[A reader of this blog, Paresh, asked me  how I was able to find out the logic behind ITL waits without having access to Oracle code. My reply was: I wrote a test case that reproduce ITL waits and a piece of code that monitors them.
Since other readers might be interested, here is the [...]]]></description>
			<content:encoded><![CDATA[<p>A reader of this blog, Paresh, <a href="/2011/04/itl-waits-changes-in-recent-releases/#comment-27702">asked me</a>  how I was able to find out the logic behind <a href="/2011/04/itl-waits-changes-in-recent-releases">ITL waits</a> without having access to Oracle code. My reply was: I wrote a test case that reproduce ITL waits and a piece of code that monitors them.</p>
<p>Since other readers might be interested, here is the shell script I wrote. Notice that it takes four parameters as input: user name, password, SID, and how long it has to wait in the monitoring phase.</p>
<p><pre>#!/bin/sh

user=$1
password=$2
sid=$3
wait=$4

#
# Setup test environment
#

sqlplus -s $user/$password@$sid &lt;&lt;END
  SET ECHO OFF TERMOUT ON FEEDBACK OFF HEADING OFF
  BEGIN
    EXECUTE IMMEDIATE &#039;DROP TABLE t PURGE&#039;;
  EXCEPTION
    WHEN OTHERS THEN NULL;
  END;
  /
  CREATE TABLE t (n NUMBER, pad VARCHAR2(50)) PCTFREE 0 INITRANS 5 TABLESPACE users;
  INSERT INTO t SELECT rownum, rpad(&#039;*&#039;,50,&#039;*&#039;) FROM dual CONNECT BY level &lt;= 134 UNION ALL SELECT 135, rpad(&#039;*&#039;,48,&#039;*&#039;) FROM dual;
  COMMIT;
  SELECT &#039;Setup correctly performed:&#039;, decode(count(DISTINCT dbms_rowid.rowid_block_number(rowid)),1,&#039;YES&#039;,&#039;NO&#039;) FROM t; 
END

#
# Produce ITL wait
#

for i in 1 2 3 4 5 6
do
  echo &#039;SET ECHO OFF TERMOUT OFF FEEDBACK OFF&#039; &gt; $i.$sid.sql
  if [[ $i = 6 ]]
  then
    # make sure that the other processes have locked one row
    echo &#039;execute dbms_lock.sleep(1)&#039; &gt;&gt; $i.$sid.sql
  fi
  echo &#039;UPDATE t SET n = n WHERE n =&#039; $i &#039;;&#039; &gt;&gt; $i.$sid.sql 
  echo &#039;SET TERMOUT OFF&#039; &gt;&gt; $i.$sid.sql
  if [[ $i &lt; 6 ]]
  then
    echo &#039;execute dbms_lock.sleep(&#039; $wait &#039;)&#039; &gt;&gt; $i.$sid.sql
  fi
  sqlplus -s $user/$password@$sid @$i.$sid.sql &amp;
done

#
# Monitor ITL wait
#

sqlplus -s $user/$password@$sid &lt;&lt;END
  SET SERVEROUTPUT ON ECHO OFF TERMOUT ON FEEDBACK OFF HEADING OFF
  SELECT * FROM v\$version WHERE rownum = 1;
  DECLARE
    l_waiter_session v\$session.sid%TYPE := NULL;
    l_blocking_session_curr v\$session.blocking_session%TYPE := NULL;
    l_blocking_session_prev v\$session.blocking_session%TYPE := NULL;
    l_seconds_in_wait_curr v\$session.seconds_in_wait%TYPE := NULL;
    l_seconds_in_wait_prev v\$session.seconds_in_wait%TYPE := NULL;
    c_sleep CONSTANT NUMBER := 0.1;
    c_iterations CONSTANT NUMBER := ceil(($wait-5)/c_sleep);
  BEGIN
    WHILE l_waiter_session IS NULL
    LOOP
      BEGIN
        SELECT sid INTO l_waiter_session
        FROM v\$session
        WHERE event = &#039;enq: TX - allocate ITL entry&#039;;
      EXCEPTION 
        WHEN no_data_found THEN NULL;
      END;
    END LOOP;
    FOR i IN 1..c_iterations
    LOOP
      BEGIN
        SELECT blocking_session, seconds_in_wait INTO l_blocking_session_curr, l_seconds_in_wait_curr
        FROM v\$session
        WHERE sid = l_waiter_session;
      EXCEPTION
        WHEN no_data_found THEN NULL;
      END;
      IF l_blocking_session_curr &lt;&gt; l_blocking_session_prev 
         OR l_blocking_session_prev IS NULL
         OR i = c_iterations 
      THEN
        dbms_output.put_line(to_char((i-1)*c_sleep,&#039;000000&#039;)||
                             &#039; blocking_session=&#039;||nvl(l_blocking_session_prev,l_blocking_session_curr)||
                             &#039; sleep=&#039;||nvl(l_seconds_in_wait_prev,l_seconds_in_wait_curr));
      END IF;
      l_blocking_session_prev := l_blocking_session_curr;
      l_seconds_in_wait_prev := l_seconds_in_wait_curr;
      dbms_lock.sleep(c_sleep);
    END LOOP;
  END;
  /
END

#
# Cleanup
#

for i in 1 2 3 4 5 6
do
  rm $i.$sid.sql
done

sleep 5

sqlplus -s $user/$password@$sid &lt;&lt;END
  SET ECHO OFF TERMOUT ON FEEDBACK OFF HEADING OFF
  DROP TABLE t PURGE;
END

exit 0
</pre></p>
<p>The outputs I got are the following:</p>
<ul>
<li>10.2.0.4</li>
</ul>
<p><pre>000000 blocking_session=136 sleep=0
000005 blocking_session=136 sleep=6
000010 blocking_session=140 sleep=3
000015 blocking_session=152 sleep=6
000020 blocking_session=159 sleep=6
029995 blocking_session=158 sleep=29979</pre></p>
<ul>
<li>10.2.0.5</li>
</ul>
<p><pre>000000 blocking_session=158 sleep=0
000001 blocking_session=158 sleep=0
000002 blocking_session=152 sleep=2
000003 blocking_session=141 sleep=0
000004 blocking_session=148 sleep=0
000005 blocking_session=140 sleep=3
000007 blocking_session=158 sleep=0
000009 blocking_session=152 sleep=3
000011 blocking_session=141 sleep=3
000013 blocking_session=148 sleep=0
000015 blocking_session=140 sleep=3
000019 blocking_session=158 sleep=3
000023 blocking_session=152 sleep=6
000027 blocking_session=141 sleep=3
000031 blocking_session=148 sleep=3
000035 blocking_session=140 sleep=6
000040 blocking_session=158 sleep=3
000045 blocking_session=152 sleep=6
000050 blocking_session=141 sleep=6
000054 blocking_session=148 sleep=3
000062 blocking_session=140 sleep=9
000067 blocking_session=158 sleep=6
000072 blocking_session=152 sleep=3
000077 blocking_session=141 sleep=6
000082 blocking_session=148 sleep=6
000098 blocking_session=140 sleep=15
000103 blocking_session=158 sleep=6
000108 blocking_session=152 sleep=3
000113 blocking_session=141 sleep=6
000118 blocking_session=148 sleep=6
000149 blocking_session=140 sleep=30
000154 blocking_session=158 sleep=6
000159 blocking_session=152 sleep=6
000164 blocking_session=141 sleep=3
000169 blocking_session=148 sleep=6
000232 blocking_session=140 sleep=63
000237 blocking_session=158 sleep=6
000242 blocking_session=152 sleep=6
000247 blocking_session=141 sleep=3
000252 blocking_session=148 sleep=6
000379 blocking_session=140 sleep=129
000383 blocking_session=158 sleep=3
000388 blocking_session=152 sleep=6
000393 blocking_session=141 sleep=6
000398 blocking_session=148 sleep=3
000651 blocking_session=140 sleep=258
000656 blocking_session=158 sleep=3
000661 blocking_session=152 sleep=6
000666 blocking_session=141 sleep=6
000671 blocking_session=148 sleep=3
001177 blocking_session=140 sleep=514
001182 blocking_session=158 sleep=6
001187 blocking_session=152 sleep=3
001192 blocking_session=141 sleep=6
001197 blocking_session=148 sleep=6
014218 blocking_session=140 sleep=13184
029995 blocking_session=140 sleep=28788</pre></p>
<ul>
<li>11.1.0.6</li>
</ul>
<p><pre>000000 blocking_session=146 sleep=0
000005 blocking_session=146 sleep=5
000010 blocking_session=129 sleep=5
000015 blocking_session=141 sleep=5
000020 blocking_session=126 sleep=5
029995 blocking_session=132 sleep=29978</pre></p>
<ul>
<li>11.1.0.7</li>
</ul>
<p><pre>000000 blocking_session=136 sleep=0
000005 blocking_session=136 sleep=5
000010 blocking_session=140 sleep=5
000015 blocking_session=132 sleep=5
000020 blocking_session=131 sleep=5
029995 blocking_session=134 sleep=29979</pre></p>
<ul>
<li>11.2.0.1</li>
</ul>
<p><pre>000000 blocking_session=131 sleep=0
000001 blocking_session=131 sleep=1
000002 blocking_session=133 sleep=1
000003 blocking_session=196 sleep=1
000004 blocking_session=67 sleep=1
000005 blocking_session=69 sleep=1
000007 blocking_session=131 sleep=2
000009 blocking_session=133 sleep=2
000011 blocking_session=196 sleep=2
000013 blocking_session=67 sleep=2
000015 blocking_session=69 sleep=2
000019 blocking_session=131 sleep=4
000023 blocking_session=133 sleep=4
000027 blocking_session=196 sleep=4
000031 blocking_session=67 sleep=4
000035 blocking_session=69 sleep=4
000040 blocking_session=131 sleep=5
000045 blocking_session=133 sleep=5
000050 blocking_session=196 sleep=5
000054 blocking_session=67 sleep=5
000062 blocking_session=69 sleep=8
000067 blocking_session=131 sleep=5
000072 blocking_session=133 sleep=5
000077 blocking_session=196 sleep=5
000082 blocking_session=67 sleep=5
000098 blocking_session=69 sleep=16
000103 blocking_session=131 sleep=5
000108 blocking_session=133 sleep=5
000113 blocking_session=196 sleep=5
000118 blocking_session=67 sleep=5
000149 blocking_session=69 sleep=32
000154 blocking_session=131 sleep=5
000159 blocking_session=133 sleep=5
000164 blocking_session=196 sleep=5
000169 blocking_session=67 sleep=5
000232 blocking_session=69 sleep=64
000237 blocking_session=131 sleep=5
000242 blocking_session=133 sleep=5
000247 blocking_session=196 sleep=5
000252 blocking_session=67 sleep=5
000379 blocking_session=69 sleep=128
000383 blocking_session=131 sleep=5
000388 blocking_session=133 sleep=5
000393 blocking_session=196 sleep=5
000398 blocking_session=67 sleep=5
000651 blocking_session=69 sleep=256
000656 blocking_session=131 sleep=5
000661 blocking_session=133 sleep=5
000666 blocking_session=196 sleep=5
000671 blocking_session=67 sleep=5
001177 blocking_session=69 sleep=512
001182 blocking_session=131 sleep=5
001187 blocking_session=133 sleep=5
001192 blocking_session=196 sleep=5
001196 blocking_session=67 sleep=5
029995 blocking_session=69 sleep=28787</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/06/itl-waits-%e2%80%93-changes-in-recent-releases-script/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>ITL Waits &#8211; Changes in Recent Releases</title>
		<link>http://antognini.ch/2011/04/itl-waits-changes-in-recent-releases/</link>
		<comments>http://antognini.ch/2011/04/itl-waits-changes-in-recent-releases/#comments</comments>
		<pubDate>Wed, 13 Apr 2011 11:43:23 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1512</guid>
		<description><![CDATA[In recent releases Oracle has silently changed the behavior of ITL waits. The aim of this post it to describe what has changed and why. But, first of all, let’s review some essential concepts about ITLs and ITL waits.
Interested Transaction List 
The Oracle database engine locks the data modified by a transaction at the row [...]]]></description>
			<content:encoded><![CDATA[<p>In recent releases Oracle has silently changed the behavior of ITL waits. The aim of this post it to describe what has changed and why. But, first of all, let’s review some essential concepts about ITLs and ITL waits.</p>
<p><strong>Interested Transaction List </strong></p>
<p>The Oracle database engine locks the data modified by a transaction at the row level. To implement this feature every data block contains a list of all transactions that are modifying it. This list is commonly called <em>interested transaction list</em> (ITL). Its purpose is twofold. First, it is used to store information to identify a transaction as well as a reference to access the undo data associated to it. Second, it is referenced by every modified or locked row to indicate which transaction it is involved.</p>
<p><strong>INITRANS</strong></p>
<p>The initial number of slots composing the ITL is set through the INITRANS parameter. Even though it can be set to 1, which is the default value as well, as of 9i at least 2 slots are always created. Note that the data dictionary lies to us on this matter. In fact, as shown in the following example, the data dictionary shows the value specified when the object was created and not the actual number of slots.</p>
<p><pre>SQL&gt; CREATE TABLE t (n NUMBER) INITRANS 1;

SQL&gt; SELECT ini_trans FROM user_tables WHERE table_name = &#039;T&#039;;

 INI_TRANS
----------
         1</pre></p>
<p><strong>MAXTRANS</strong></p>
<p>There is a maximum number of slots an ITL can contain. The actual maximum number depends on the blocks size. For example, an 8KB block can have up to 169 slots. Up to 9i the maximum is limited by the MAXTRANS parameter as well. As of 10g, however, this parameter is deprecated and, therefore, no longer honored. In the same way as for INITRANS, the data dictionary shows the value specified when the object was created and not the actual maximum number of slots.<br />
Also note that while creating an object the database engine checks whether the MAXTRANS value is not greater than 255. And, if it is greater, it raises an ORA-02209 (invalid MAXTRANS option value).</p>
<p><strong>ITL Waits</strong></p>
<p>When a session requires a slot but all the available ones are in use by other active transactions, the database engine tries to dynamically create a new slot. This is of course only possible when a) the maximum number of slots was not already allocated b) enough free space (one slot occupies 24 bytes) is available in the block itself. If a new slot cannot be created, the session requiring it hits a so-called <em>ITL wait</em>. Note that the name of the actual wait event is called “enq: TX &#8211; allocate ITL entry”.<br />
It is essential to point out that a session does not wait on the first slot becoming free. Instead, it probes, round-robin, the available slots to find out one that becomes free. And, while doing so, it waits few seconds on every one it probes. When during this short wait the slot becomes free, it uses it. Otherwise, it tries with another slot.<br />
The actual implementation for finding a free slot is what Oracle changed in recent releases. So, let’s describe what the behavior in recent releases is.</p>
<p><strong>ITL Waits in 11gR1</strong></p>
<p>In 11.1.0.6 and 11.1.0.7 a session waits at most one time on every slot. For all slots but one it waits up to 5 seconds. For the other one it might wait indefinitely. The following pseudo code illustrates this (you should consider the variable called &#8220;itl&#8221; as an array referencing/containing all ITL slots).</p>
<p><pre>FOR i IN itl.FIRST..itl.LAST
LOOP
  EXIT WHEN itl(i) IS FREE
  IF i &lt;&gt; itl.LAST
  THEN WAIT ON itl(i) FOR 5 SECONDS
  ELSE WAIT ON itl(i) FOREVER
  END IF
END LOOP</pre></p>
<p>The problem of this algorithm is that an “unlucky” session might wait much longer than necessary. In fact, once it enters the WAIT FOREVER status, it no longer considers the other slots.</p>
<p><strong>ITL Waits in 11gR2</strong></p>
<p>In 11.2.0.1 and 11.2.0.2 a session might wait several times for the same slot. Initially the wait is short. As the time passes, the wait time increases exponentially based on the formula “wait time = power(2,iteration-1)”. For all slots but one there is a maximum wait time of 5 seconds, though. For the other one, and for the first 10 iterations only, the wait time is computed with the very same formula. Then, during the 11th iteration, the session waits indefinitely. The following pseudo code illustrates this.</p>
<p><pre>iteration = 0
LOOP
  iteration++
  FOR i IN itl.FIRST..itl.LAST
  LOOP
    EXIT WHEN itl(i) IS FREE
    IF i &lt;&gt; itl.LAST
    THEN WAIT ON itl(i) FOR min(power(2,iteration-1),5) SECONDS
    ELSIF iteration &lt;= 10
    THEN WAIT ON itl(i) FOR power(2,iteration-1) SECONDS
    ELSE WAIT ON itl(i) FOREVER
    END IF
  END LOOP
  EXIT WHEN free_itl_found
END LOOP</pre></p>
<p>The advantage of this algorithm is that a session might probe several time all the available slots and, as a result, enters the WAIT FOREVER status after about 20 minutes only.</p>
<p><strong>ITL Waits in 9i/10g</strong></p>
<p>Up to 10.2.0.4 the behavior is similar to 11gR1. The only noticeable difference is that the wait time is not always 5 seconds. Instead, it is either 3 or 6 seconds. I was not able to spot a rule behind the choice between the two durations. So, there might be some randomness involved.<br />
In 10.2.0.5 the behavior is similar to 11gR2. Also in this case the only noticeable difference is that the maximum wait time is not always 5 seconds. Instead, as in releases up to 10.2.0.4, it is either 3 or 6 seconds.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/04/itl-waits-changes-in-recent-releases/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Scripts to Download Documentation</title>
		<link>http://antognini.ch/2011/04/scripts-to-download-documentation/</link>
		<comments>http://antognini.ch/2011/04/scripts-to-download-documentation/#comments</comments>
		<pubDate>Fri, 01 Apr 2011 01:40:02 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Documentation]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1502</guid>
		<description><![CDATA[In this post I pointed out that I like to have a copy of the documentation in PDF format on my notebook. In the same post, and its comments, I also described how I generate the scripts I use to download the files. Recently I updated the scripts and, as a result, I thought to [...]]]></description>
			<content:encoded><![CDATA[<p>In <a href="/2009/09/script-to-download-11gr2-documentation/">this post</a> I pointed out that I like to have a copy of the documentation in PDF format on my notebook. In the same post, and its comments, I also described how I generate the scripts I use to download the files. Recently I updated the scripts and, as a result, I thought to share them with you. So, below you find the CMD and SH scripts for the documentation related to 10.2, 11.1 and 11.2.</p>
<ul>
<li><a href="/downloads/download102.cmd">download102.cmd</a></li>
<li><a href="/downloads/download102.sh">download102.sh</a></li>
<li><a href="/downloads/download111.cmd">download111.cmd</a></li>
<li><a href="/downloads/download111.sh">download111.sh</a></li>
<li><a href="/downloads/download112.cmd">download112.cmd</a></li>
<li><a href="/downloads/download112.sh">download112.sh</a></li>
</ul>
<p>I hope you find them useful.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/04/scripts-to-download-documentation/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>IS NULL Conditions and B-tree Indexes</title>
		<link>http://antognini.ch/2011/02/is-null-conditions-and-b-tree-indexes/</link>
		<comments>http://antognini.ch/2011/02/is-null-conditions-and-b-tree-indexes/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 10:01:56 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Query Optimizer]]></category>
		<category><![CDATA[TOP]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1479</guid>
		<description><![CDATA[At page 383 of my book I wrote the following sentence (BTW, the same information is also provided by Table 9-3 at page 381):
With B-tree indexes, IS NULL conditions can be applied only through composite B-tree indexes when several SQL conditions are applied and at least one of them is not based on IS NULL [...]]]></description>
			<content:encoded><![CDATA[<p>At page 383 of <a href="/top">my book</a> I wrote the following sentence (BTW, the same information is also provided by Table 9-3 at page 381):</p>
<blockquote><p>With B-tree indexes, IS NULL conditions can be applied only through composite B-tree indexes when several SQL conditions are applied and at least one of them is not based on IS NULL or an inequality.</p></blockquote>
<p>The text continues by showing the following examples (notice that in both cases the IS NULL predicate is applied through an access predicate):</p>
<p><pre>SELECT /*+ index(t) */ * FROM t WHERE n1 = 6 AND n2 IS NULL

Plan hash value: 780655320

----------------------------------------------
| Id  | Operation                   | Name   |
----------------------------------------------
|   0 | SELECT STATEMENT            |        |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |
|*  2 |   INDEX RANGE SCAN          | I_N123 |
----------------------------------------------

   2 - access(&quot;N1&quot;=6 AND &quot;N2&quot; IS NULL)

SELECT /*+ index(t) */ * FROM t WHERE n1 IS NULL AND n2 = 8

Plan hash value: 780655320

----------------------------------------------
| Id  | Operation                   | Name   |
----------------------------------------------
|   0 | SELECT STATEMENT            |        |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |
|*  2 |   INDEX RANGE SCAN          | I_N123 |
----------------------------------------------

   2 - access(&quot;N1&quot; IS NULL AND &quot;N2&quot;=8)
       filter(&quot;N2&quot;=8)</pre></p>
<p>When I wrote that sentence I didn&#8217;t think about one case that, according to it, specifically the part &#8220;is not based on IS NULL or an inequality&#8221;, is not covered. In fact, as the following examples show, it is also possible to apply an IS NULL predicate when the other one is an IS NOT NULL. It is especially interesting to notice that the access predicate doesn&#8217;t reference at all the NOT NULL column!</p>
<p><pre>SELECT /*+ index(t) */ * FROM t WHERE n1 IS NULL AND n2 IS NOT NULL

Plan hash value: 780655320

----------------------------------------------
| Id  | Operation                   | Name   |
----------------------------------------------
|   0 | SELECT STATEMENT            |        |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |
|*  2 |   INDEX RANGE SCAN          | I_N123 |
----------------------------------------------

   2 - access(&quot;N1&quot; IS NULL)
       filter(&quot;N2&quot; IS NOT NULL)

SELECT /*+ index(t) */ * FROM t WHERE n1 IS NOT NULL AND n2 IS NULL

Plan hash value: 3029444779

----------------------------------------------
| Id  | Operation                   | Name   |
----------------------------------------------
|   0 | SELECT STATEMENT            |        |
|   1 |  TABLE ACCESS BY INDEX ROWID| T      |
|*  2 |   INDEX SKIP SCAN           | I_N123 |
----------------------------------------------

   2 - access(&quot;N2&quot; IS NULL)
       filter((&quot;N2&quot; IS NULL AND &quot;N1&quot; IS NOT NULL))</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/02/is-null-conditions-and-b-tree-indexes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Parallel Full Table Scans Do Not Always Perform Direct Reads</title>
		<link>http://antognini.ch/2010/09/parallel-full-table-scans-do-not-always-perform-direct-reads/</link>
		<comments>http://antognini.ch/2010/09/parallel-full-table-scans-do-not-always-perform-direct-reads/#comments</comments>
		<pubDate>Sun, 12 Sep 2010 10:52:48 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Parallel Processing]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1287</guid>
		<description><![CDATA[Even though in general parallel full table scans performs direct reads, some exceptions exist. The aim of this post is to show such an exception. 
For test purposes I build in my own schema a copy of the SH.SALES table (the one distributed by Oracle with the demo schemas…). On that table I build an [...]]]></description>
			<content:encoded><![CDATA[<p>Even though in general parallel full table scans performs direct reads, some exceptions exist. The aim of this post is to show such an exception. </p>
<p>For test purposes I build in my own schema a copy of the SH.SALES table (the one distributed by Oracle with the demo schemas…). On that table I build an index on the TIME_ID column and, at the same time, I trace the execution. The statements I use are the following.</p>
<p><pre>execute dbms_monitor.session_trace_enable
CREATE INDEX sales_time_id ON sales (time_id) PARALLEL 2 ONLINE;
execute dbms_monitor.session_trace_disable</pre></p>
<p>Since the index is built in parallel (DOP=2), 5 processes are used to run the statement: the query coordinator, 2 slaves for reading the table, and 2 slaves for building the index. Based on the data provided by extended SQL trace let’s have a look to some information about the execution.</p>
<ul>
<li>Execution plan (without runtime statistics and query optimizer estimations): both the build of the index and the full table scan are performed in parallel.</li>
</ul>
<p><pre>Operation
----------------------------------------
PX COORDINATOR
  PX SEND QC (ORDER) :TQ10001
    INDEX BUILD NON UNIQUE SALES_TIME_ID
      SORT CREATE INDEX
        PX RECEIVE
          PX SEND RANGE :TQ10000
            PX BLOCK ITERATOR
              TABLE ACCESS FULL SALES</pre></p>
<ul>
<li>Resource usage profile of the query coordinator: no real work is performed, it&#8217;s just coordination work&#8230;</li>
</ul>
<p><pre>                                               Total            Number of     Duration per
Component                               Duration [s]       %       Events       Events [s]
----------------------------------- ---------------- ------- ------------ ----------------
PX Deq: Execute Reply                          1.928  79.044           44            0.044
recursive statements                           0.427  17.487          n/a              n/a
CPU                                            0.024   0.984          n/a              n/a
os thread startup                              0.017   0.690            1            0.017
log file sync                                  0.014   0.569            2            0.007
PX Deq: Parse Reply                            0.014   0.568            4            0.003
enq: CR - block range reuse ckpt               0.005   0.202            2            0.002
PX Deq: Join ACK                               0.004   0.145            5            0.001
SQL*Net message from client                    0.002   0.098            1            0.002
PX qref latch                                  0.002   0.075            1            0.002
PX Deq: Table Q qref                           0.001   0.058            1            0.001
enq: RO - fast object reuse                    0.001   0.028            1            0.001
PX Deq: Signal ACK EXT                         0.001   0.025            6            0.000
PX Deq: Slave Session Stats                    0.000   0.014            3            0.000
reliable message                               0.000   0.006            2            0.000
latch: call allocation                         0.000   0.004            1            0.000
db file sequential read                        0.000   0.004            6            0.000
rdbms ipc reply                                0.000   0.002            2            0.000
SQL*Net message to client                      0.000   0.000            1            0.000
----------------------------------- ---------------- -------
Total                                          2.439 100.000</pre></p>
<ul>
<li>Resource usage profile of one of the slaves building the index: direct writes are used to store the index.</li>
</ul>
<p><pre>                                               Total            Number of     Duration per
Component                               Duration [s]       %       Events       Events [s]
----------------------------------- ---------------- ------- ------------ ----------------
direct path write                              0.982  49.671          376            0.003
CPU                                            0.781  39.508          n/a              n/a
PX Deq: Table Q Normal                         0.177   8.957          130            0.001
PX Deq: Execution Msg                          0.012   0.595            4            0.003
cursor: pin S wait on X                        0.011   0.535            1            0.011
recursive statements                           0.008   0.420          n/a              n/a
log file sync                                  0.005   0.247            3            0.002
Disk file operations I/O                       0.001   0.030            1            0.001
PX Deq: Slave Session Stats                    0.000   0.021            1            0.000
reliable message                               0.000   0.011            1            0.000
rdbms ipc reply                                0.000   0.006            2            0.000
----------------------------------- ---------------- -------
Total                                          1.977 100.000</pre></p>
<ul>
<li>Resource usage profile of one of the slaves scanning the table: instead of performing direct reads, “regular” buffered reads are performed (notice the <em>db file scattered read</em> event).</li>
</ul>
<p><pre>                                               Total            Number of     Duration per
Component                               Duration [s]       %       Events       Events [s]
----------------------------------- ---------------- ------- ------------ ----------------
PX Deq: Execution Msg                          1.571  80.295           23            0.068
CPU                                            0.311  15.889          n/a              n/a
PX Deq Credit: need buffer                     0.055   2.794          432            0.000
db file scattered read                         0.016   0.835           55            0.000
PX Deq: Table Q Get Keys                       0.002   0.109            1            0.002
Disk file operations I/O                       0.001   0.032            1            0.001
PX Deq Credit: send blkd                       0.001   0.028            1            0.001
PX Deq: Slave Session Stats                    0.000   0.010            1            0.000
db file sequential read                        0.000   0.005            4            0.000
PX qref latch                                  0.000   0.002            1            0.000
latch: cache buffers chains                    0.000   0.000            1            0.000
----------------------------------- ---------------- -------
Total                                          1.957 100.000</pre></p>
<p>As pointed out by the last resource usage profile no direct reads are performed. Why? In this case it is because the ONLINE option was specified. By the way, I do not know why there is such a limitation&#8230; Anway, without this option, for one of the slaves reading the table the following resource usage profile is used (notice the <em>direct path read</em> event). I do not show the other resource usage profiles and the execution plan because they do not change.</p>
<p><pre>                                               Total            Number of     Duration per
Component                               Duration [s]       %       Events       Events [s]
----------------------------------- ---------------- ------- ------------ ----------------
PX Deq: Execution Msg                          1.499  80.358           22            0.068
CPU                                            0.278  14.897          n/a              n/a
PX Deq Credit: need buffer                     0.071   3.790          504            0.000
direct path read                               0.012   0.630           52            0.000
PX Deq Credit: send blkd                       0.003   0.176            2            0.002
PX Deq: Table Q Get Keys                       0.001   0.047            2            0.000
PX Deq: Slave Session Stats                    0.001   0.041            1            0.001
Disk file operations I/O                       0.001   0.033            1            0.001
library cache: mutex X                         0.000   0.025            1            0.000
db file sequential read                        0.000   0.002            2            0.000
asynch descriptor resize                       0.000   0.001           18            0.000
----------------------------------- ---------------- -------
Total                                          1.866 100.000</pre></p>
<p>In summary, do not expect to always see direct reads when a parallel full table scan is performed.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2010/09/parallel-full-table-scans-do-not-always-perform-direct-reads/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Partition-Wise Join of List-Partitioned Tables</title>
		<link>http://antognini.ch/2010/08/partition-wise-join-of-list-partitioned-tables/</link>
		<comments>http://antognini.ch/2010/08/partition-wise-join-of-list-partitioned-tables/#comments</comments>
		<pubDate>Mon, 02 Aug 2010 09:59:08 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Partitioning]]></category>
		<category><![CDATA[Query Optimizer]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1202</guid>
		<description><![CDATA[When two tables are equi-partitioned on their join keys, the query optimizer is able to take advantage of partition-wise joins. To make sure that the tables are equi-partitioned, as of Oracle Database 11g reference partitioning can be used. In fact, per definition, with reference partitioning all “related” tables have exactly the same partitioning schema. If [...]]]></description>
			<content:encoded><![CDATA[<p>When two tables are equi-partitioned on their join keys, the query optimizer is able to take advantage of partition-wise joins. To make sure that the tables are equi-partitioned, as of Oracle Database 11g reference partitioning can be used. In fact, per definition, with reference partitioning all “related” tables have exactly the same partitioning schema. If you are not using reference partitioning, you must be very careful that the tables are effectively partitioned in very same way. For range and hash partitioned tables this is usually not a problem. However, when using list partitioning, it is quite easy to make a mistake. The reason is that the partitions can be defined in any order. Let’s have a look to an example based on the following two tables.</p>
<p><pre>SQL&gt; CREATE TABLE t1p
  2  PARTITION BY LIST (pkey) (
  3    PARTITION p_0 VALUES (0),
  4    PARTITION p_1 VALUES (1),
  5    PARTITION p_2 VALUES (2),
  6    PARTITION p_3 VALUES (3),
  7    PARTITION p_4 VALUES (4),
  8    PARTITION p_5 VALUES (5),
  9    PARTITION p_6 VALUES (6),
 10    PARTITION p_7 VALUES (7),
 11    PARTITION p_8 VALUES (8),
 12    PARTITION p_9 VALUES (9)
 13  )
 14  AS
 15  SELECT rownum AS num, mod(rownum,10) AS pkey, dbms_random.string(&#039;p&#039;,50) AS pad
 16  FROM dual
 17  CONNECT BY level &lt;= 10000;

SQL&gt; CREATE TABLE t2p
  2  PARTITION BY LIST (pkey) (
  3    PARTITION p_0 VALUES (0),
  4    PARTITION p_1 VALUES (1),
  5    PARTITION p_2 VALUES (2),
  6    PARTITION p_3 VALUES (3),
  7    PARTITION p_5 VALUES (5),
  8    PARTITION p_4 VALUES (4),
  9    PARTITION p_6 VALUES (6),
 10    PARTITION p_7 VALUES (7),
 11    PARTITION p_8 VALUES (8),
 12    PARTITION p_9 VALUES (9)
 13  )
 14  AS
 15  SELECT rownum AS num, mod(rownum,10) AS pkey, dbms_random.string(&#039;p&#039;,50) AS pad
 16  FROM dual
 17  CONNECT BY level &lt;= 10000;

SQL&gt; BEGIN
  2    dbms_stats.gather_table_stats(user,&#039;t1p&#039;);
  3    dbms_stats.gather_table_stats(user,&#039;t2p&#039;);
  4  END;
  5  /</pre></p>
<p>Even though they are logically equivalent, as shown in the following execution plan, with them partition-wise joins cannot be used.</p>
<p><pre>SQL&gt; EXPLAIN PLAN FOR SELECT * FROM t1p JOIN t2p USING (num, pkey);

SQL&gt; SELECT * FROM table(dbms_xplan.display(format=&gt;&#039;basic&#039;));

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

Plan hash value: 3059592055

------------------------------------
| Id  | Operation           | Name |
------------------------------------
|   0 | SELECT STATEMENT    |      |
|   1 |  HASH JOIN          |      |
|   2 |   PARTITION LIST ALL|      |
|   3 |    TABLE ACCESS FULL| T1P  |
|   4 |   PARTITION LIST ALL|      |
|   5 |    TABLE ACCESS FULL| T2P  |
------------------------------------</pre></p>
<p>The difference in the order of the partitions can also be confirmed by a query like the following one.</p>
<p><pre>SQL&gt; SELECT t1p.high_value,
  2         t1p.partition_position AS pos_t1p,
  3         t2p.partition_position AS pos_t2p,
  4         decode(t1p.partition_position, t2p.partition_position, &#039;Y&#039;, &#039;N&#039;) AS equal
  5  FROM user_tab_partitions t1p JOIN user_tab_partitions t2p ON t1p.partition_name = t2p.partition_name
  6  WHERE t1p.table_name = &#039;T1P&#039;
  7  AND t2p.table_name = &#039;T2P&#039;;

HIGH_VALUE   POS_T1P  POS_T2P EQUAL
----------- -------- -------- ------
0                  1        1 Y
1                  2        2 Y
2                  3        3 Y
3                  4        4 Y
5                  6        5 N
4                  5        6 N
6                  7        7 Y
7                  8        8 Y
8                  9        9 Y
9                 10       10 Y</pre></p>
<p>It goes without saying that to solve the problem it is necessary to reorder the partitions. To do so it is enough to move the out-of-order partitions. To avoid a double storage of the data a series of ALTER TABLE EXCHANGE/DROP/ADD/EXCHANGE statements can be used.</p>
<ul>
<li>Move the P5 partition of the T1P table</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t1p_5 AS
  2  SELECT *
  3  FROM t1p PARTITION (p_5)
  4  WHERE 1 = 0;

SQL&gt; ALTER TABLE t1p EXCHANGE PARTITION p_5 WITH TABLE t1p_5;

SQL&gt; ALTER TABLE t1p DROP PARTITION p_5;

SQL&gt; ALTER TABLE t1p ADD PARTITION p_5 VALUES (5);

SQL&gt; ALTER TABLE t1p EXCHANGE PARTITION p_5 WITH TABLE t1p_5;

SQL&gt; DROP TABLE t1p_5 PURGE;</pre></p>
<ul>
<li>Move the P5 partition of the T2P table</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t2p_5 AS
  2  SELECT *
  3  FROM t2p PARTITION (p_5)
  4  WHERE 1 = 0;

SQL&gt; ALTER TABLE t2p EXCHANGE PARTITION p_5 WITH TABLE t2p_5;

SQL&gt; ALTER TABLE t2p DROP PARTITION p_5;

SQL&gt; ALTER TABLE t2p ADD PARTITION p_5 VALUES (5);

SQL&gt; ALTER TABLE t2p EXCHANGE PARTITION p_5 WITH TABLE t2p_5;

SQL&gt; DROP TABLE t2p_5 PURGE;</pre></p>
<ul>
<li>Check whether the order is ok</li>
</ul>
<p><pre>SQL&gt; SELECT t1p.high_value,
  2         t1p.partition_position AS pos_t1p,
  3         t2p.partition_position AS pos_t2p,
  4         decode(t1p.partition_position, t2p.partition_position, &#039;Y&#039;, &#039;N&#039;) AS equal
  5  FROM user_tab_partitions t1p JOIN user_tab_partitions t2p ON t1p.partition_name = t2p.partition_name
  6  WHERE t1p.table_name = &#039;T1P&#039;
  7  AND t2p.table_name = &#039;T2P&#039;;

HIGH_VALUE   POS_T1P  POS_T2P EQUAL
----------- -------- -------- ------
0                  1        1 Y
1                  2        2 Y
2                  3        3 Y
3                  4        4 Y
4                  5        5 Y
6                  6        6 Y
7                  7        7 Y
8                  8        8 Y
9                  9        9 Y
5                 10       10 Y</pre></p>
<p>After these operations partition-wise joins are allowed. The following execution plan confirms this. </p>
<p><pre>SQL&gt; SELECT * FROM table(dbms_xplan.display(format=&gt;&#039;basic&#039;));

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

Plan hash value: 1324269388

------------------------------------
| Id  | Operation           | Name |
------------------------------------
|   0 | SELECT STATEMENT    |      |
|   1 |  PARTITION LIST ALL |      |
|   2 |   HASH JOIN         |      |
|   3 |    TABLE ACCESS FULL| T1P  |
|   4 |    TABLE ACCESS FULL| T2P  |
------------------------------------</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2010/08/partition-wise-join-of-list-partitioned-tables/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Optimizer Mode Mismatch Does Not Prevent Sharing of Child Cursor!?!?</title>
		<link>http://antognini.ch/2010/06/optimizer-mode-mismatch-does-not-prevent-sharing-of-child-cursor/</link>
		<comments>http://antognini.ch/2010/06/optimizer-mode-mismatch-does-not-prevent-sharing-of-child-cursor/#comments</comments>
		<pubDate>Thu, 03 Jun 2010 17:40:03 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[Query Optimizer]]></category>
		<category><![CDATA[SQL Trace]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1113</guid>
		<description><![CDATA[The aim of this post is to describe a strange (buggy) situation that I observed recently. But before doing that, I shortly summarize what a parent cursor and a child cursor are as well as when they can be shared. By the way, I borrowed this description from the pages 20/21 of my book. Hence, [...]]]></description>
			<content:encoded><![CDATA[<p>The aim of this post is to describe a strange (buggy) situation that I observed recently. But before doing that, I shortly summarize what a parent cursor and a child cursor are as well as when they can be shared. By the way, I borrowed this description from the pages 20/21 of my <a href="/top">book</a>. Hence, if you are interested in more information about this topic refer to it…</p>
<p>The result of a parse operation is a parent cursor and a child cursor stored in the library cache.</p>
<p>The key information related to a parent cursor is the text of the SQL statement. Therefore, several SQL statements share the same parent cursor if their text is exactly the same (note that there is at least an exception to this, specifically when cursor sharing is used). In the following example, four SQL statements are executed. Two have the same text. Two others differ only because of lowercase and uppercase letters or blanks. Through the V$SQLAREA view, it is possible to confirm that three distinct parent cursors were created.</p>
<p><pre>SQL&gt; ALTER SYSTEM FLUSH SHARED_POOL;

SQL&gt; SELECT * FROM t WHERE n = 1234;

SQL&gt; select * from t where n = 1234;

SQL&gt; SELECT * FROM t WHERE n=1234;

SQL&gt; SELECT * FROM t WHERE n = 1234;

SQL&gt; SELECT sql_id, sql_text, executions
  2  FROM v$sqlarea
  3  WHERE sql_text LIKE &#039;%1234&#039;;

SQL_ID        SQL_TEXT                          EXECUTIONS
------------- --------------------------------- ----------
2254m1487jg50 select * from t where n = 1234             1
g9y3jtp6ru4cb SELECT * FROM t WHERE n = 1234             2
7n8p5s2udfdsn SELECT * FROM t WHERE n=1234               1</pre></p>
<p>The key information related to a child cursor is the execution plan and the execution environment related to it. The execution environment is important because if it changes, the execution plan might change as well. As a result, several SQL statements are able to share the same child cursor only if they share the same parent cursor and their execution environments are compatible. To illustrate, the same SQL statement is executed with two different values of the initialization OPTIMIZER_MODE parameter. The result is that a single parent cursor and two child cursors are created.</p>
<p><pre>SQL&gt; ALTER SESSION SET optimizer_mode = all_rows;

SQL&gt; SELECT count(*) FROM t;

COUNT(*)
----------
      1000

SQL&gt; ALTER SESSION SET optimizer_mode = first_rows_10;

SQL&gt; SELECT count(*) FROM t;

COUNT(*)
----------
      1000

SQL&gt; SELECT sql_id, child_number, sql_text, optimizer_mode, plan_hash_value
  2  FROM v$sql
  3  WHERE sql_id = (SELECT prev_sql_id
  4  FROM v$session
  5  WHERE sid = sys_context(&#039;userenv&#039;,&#039;sid&#039;));

SQL_ID        CHILD_NUMBER SQL_TEXT               OPTIMIZER_MODE PLAN_HASH_VALUE
------------- ------------ ---------------------- -------------- ---------------
5tjqf7sx5dzmj            0 SELECT count(*) FROM t ALL_ROWS            2966233522
5tjqf7sx5dzmj            1 SELECT count(*) FROM t FIRST_ROWS          2966233522</pre></p>
<p>To know which mismatch led to several child cursors, you can query the V$SQL_SHARED_CURSOR view.</p>
<p><pre>SQL&gt; SELECT child_number, optimizer_mode_mismatch
  2  FROM v$sql_shared_cursor
  3  WHERE sql_id = &#039;5tjqf7sx5dzmj&#039;;

CHILD_NUMBER OPTIMIZER_MODE_MISMATCH
------------ -----------------------
           0 N
           1 Y</pre></p>
<p>So far, so good… Now, let’s see what’s strange…</p>
<p>The interesting thing to point out about the previous example is that while I set FIRST_ROWS_10 as optimizer mode, the V$SQL view displayed the value FIRST_ROWS. Mhmm… That’s strange… They are two different optimizer modes. They cannot be considered equivalent. What are the implications? It is just the view that provides the wrong information or the database engine is able to share the same child cursor even with two different values of the OPTIMIZER_MODE parameter? Let’s try it with FIRST_ROWS (i.e. without “_10”)…</p>
<p><pre> SQL&gt; ALTER SESSION SET optimizer_mode = first_rows;

SQL&gt; SELECT sql_id, child_number, sql_text, optimizer_mode, executions
  2  FROM v$sql
  3  WHERE sql_id = (SELECT prev_sql_id
  4                  FROM v$session
  5                  WHERE sid = sys_context(&#039;userenv&#039;,&#039;sid&#039;));

SQL_ID        CHILD_NUMBER SQL_TEXT                          OPTIMIZER_MODE EXECUTIONS
------------- ------------ --------------------------------- -------------- ----------
5tjqf7sx5dzmj            0 SELECT count(*) FROM t            ALL_ROWS                1
5tjqf7sx5dzmj            1 SELECT count(*) FROM t            FIRST_ROWS              2</pre></p>
<p>Oh, damn! Even though the OPTIMIZER MODE is set to a different value the same child cursor is used. Since in this particular situation the execution plans associated to both child cursors are the same (their hash value are equal), it’s not a real problem. But, in practice, it might be possible that two different optimizer modes lead to different execution plans. The following example illustrates this.</p>
<ul>
<li>Build a table for the test:</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t AS
  2  SELECT rownum AS id, rpad(&#039;*&#039;,500,&#039;*&#039;) AS pad
  3  FROM dual
  4  CONNECT BY level &lt;= 1000;

SQL&gt; CREATE UNIQUE INDEX i ON t (id);

SQL&gt; execute dbms_stats.gather_table_stats(user, &#039;T&#039;)</pre></p>
<ul>
<li>Show that different values of the OPTIMIZER_MODE parameter lead to different execution plans:</li>
</ul>
<p><pre>SQL&gt; ALTER SESSION SET optimizer_mode = FIRST_ROWS_1;

SQL&gt; EXPLAIN PLAN FOR SELECT * FROM t WHERE id &lt;= 500;

SQL&gt; SELECT * FROM table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
Plan hash value: 242607798

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

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

   2 - access(&quot;ID&quot;&lt;=500)

SQL&gt; ALTER SESSION SET optimizer_mode = FIRST_ROWS_1000;

SQL&gt; EXPLAIN PLAN FOR SELECT * FROM t WHERE id &lt;= 500;

SQL&gt; SELECT * FROM table(dbms_xplan.display);

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

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |   500 |   246K|    10   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| T    |   500 |   246K|    10   (0)| 00:00:01 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;ID&quot;&lt;=500)</pre></p>
<ul>
<li>Execute the test query with both values of the OPTIMIZER_MODE parameter:</li>
</ul>
<p><pre>SQL&gt; ALTER SYSTEM FLUSH SHARED_POOL;

SQL&gt; ALTER SESSION SET optimizer_mode = FIRST_ROWS_1;

SQL&gt; SELECT * FROM t WHERE id &lt;= 500;

        ID PAD
---------- ----------
         1 **********
         2 **********
…
       499 **********
       500 **********

SQL&gt; ALTER SESSION SET optimizer_mode = FIRST_ROWS_1000;

SQL&gt; SELECT * FROM t WHERE id &lt;= 500;

        ID PAD
---------- ----------
         1 **********
         2 **********
…
       499 **********
       500 **********</pre></p>
<ul>
<li>Show that a single execution plan was used for both executions:</li>
</ul>
<p><pre>SQL&gt; SELECT * FROM table(dbms_xplan.display_cursor(NULL,NULL));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------
SQL_ID  2vw03p929jzgz, child number 0
-------------------------------------
SELECT * FROM t WHERE id &lt;= 500

Plan hash value: 242607798

------------------------------------------------------------------------------------
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |      |       |       |     3 (100)|          |
|   1 |  TABLE ACCESS BY INDEX ROWID| T    |     3 |  1515 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | I    |       |       |     2   (0)| 00:00:01 |
------------------------------------------------------------------------------------

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

   2 - access(&quot;ID&quot;&lt;=500)

SQL&gt; SELECT sql_id, child_number, executions, optimizer_mode
  2  FROM v$sql
  3  WHERE sql_id = &#039;2vw03p929jzgz&#039;;

SQL_ID        CHILD_NUMBER EXECUTIONS OPTIMIZER_MODE
------------- ------------ ---------- --------------
2vw03p929jzgz            0          2 FIRST_ROWS</pre></p>
<p>Even though it is not very likely that this bug (yes, in my opinion something like this cannot be considered a restriction of the implementation…) has an impact on a production system, I really don’t understand why the developers didn’t implement it correctly. It should not be that difficult to manage a byte containing the information about the used optimizer mode! Note that this is not the only case where something like that happens with the first rows optimizer mode. For example, also in a trace file generated through SQL trace no difference is made between the old and the new first row optimizer. So, it seams that they really got it wrong.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2010/06/optimizer-mode-mismatch-does-not-prevent-sharing-of-child-cursor/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Native Full Outer Join Officially Available in 10.2.0.5</title>
		<link>http://antognini.ch/2010/05/native-full-outer-join-officially-available-in-10-2-0-5/</link>
		<comments>http://antognini.ch/2010/05/native-full-outer-join-officially-available-in-10-2-0-5/#comments</comments>
		<pubDate>Tue, 04 May 2010 11:25:26 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR2]]></category>
		<category><![CDATA[Query Optimizer]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1041</guid>
		<description><![CDATA[Today I installed for the first time the patchset 10.2.0.5. While reading the README file, I noticed the following piece of information.
To enable a new native full outer join implementation in the database, a user has to set the following underscore parameter:
_optimizer_native_full_outer_join =force
You can set this parameter for the system or for a specific session.
Besides [...]]]></description>
			<content:encoded><![CDATA[<p>Today I installed for the first time the patchset 10.2.0.5. While reading the README file, I noticed the following piece of information.</p>
<blockquote><p>To enable a new native full outer join implementation in the database, a user has to set the following underscore parameter:</p>
<p>_optimizer_native_full_outer_join =force</p>
<p>You can set this parameter for the system or for a specific session.</p>
<p>Besides dramatically improving the performance of a full outer join, the new implementation fixes a variety of issues, for examples a variety of ORA-942 (table or view doesn&#8217;t exists) and ORA-4331 (unable to allocate string bytes of shared memory) errors.</p>
<p>This issue is tracked with Oracle bug 6322672.</p></blockquote>
<p>Great! At last we can officially take advantage of native full outer join also in 10.2 (the feature was officially introduced in 11.1, but was already &#8220;available&#8221; in 10.2.0.3). </p>
<p>Here is an example:</p>
<ul>
<li>By default native full outer joins are disabled (notice the implementation with the UNION ALL operation):</li>
</ul>
<p><pre>SQL&gt; SELECT * FROM emp FULL OUTER JOIN dept USING (deptno);

Execution Plan
----------------------------------------------------------
Plan hash value: 2291915024

---------------------------------------------
| Id  | Operation            | Name         |
---------------------------------------------
|   0 | SELECT STATEMENT     |              |
|   1 |  VIEW                |              |
|   2 |   UNION-ALL          |              |
|*  3 |    HASH JOIN OUTER   |              |
|   4 |     TABLE ACCESS FULL| EMP          |
|   5 |     TABLE ACCESS FULL| DEPT         |
|   6 |    NESTED LOOPS ANTI |              |
|   7 |     TABLE ACCESS FULL| DEPT         |
|*  8 |     INDEX RANGE SCAN | EMP_DEPTNO_I |
---------------------------------------------

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

   3 - access(&quot;EMP&quot;.&quot;DEPTNO&quot;=&quot;DEPT&quot;.&quot;DEPTNO&quot;(+))
   8 - access(&quot;EMP&quot;.&quot;DEPTNO&quot;=&quot;DEPT&quot;.&quot;DEPTNO&quot;)</pre></p>
<ul>
<li>As suggested by the README file, the feature can be enabled at the session level:</li>
</ul>
<p><pre>SQL&gt; ALTER SESSION SET &quot;_optimizer_native_full_outer_join&quot; = force;

SQL&gt; SELECT * FROM emp FULL OUTER JOIN dept USING (deptno);

Execution Plan
----------------------------------------------------------
Plan hash value: 51889263

------------------------------------------
| Id  | Operation             | Name     |
------------------------------------------
|   0 | SELECT STATEMENT      |          |
|   1 |  VIEW                 | VW_FOJ_0 |
|*  2 |   HASH JOIN FULL OUTER|          |
|   3 |    TABLE ACCESS FULL  | DEPT     |
|   4 |    TABLE ACCESS FULL  | EMP      |
------------------------------------------

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

   2 - access(&quot;EMP&quot;.&quot;DEPTNO&quot;=&quot;DEPT&quot;.&quot;DEPTNO&quot;)</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2010/05/native-full-outer-join-officially-available-in-10-2-0-5/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

