<?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/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Tony's Oracle Tips</title>
	<atom:link href="http://tonyhasler.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://tonyhasler.wordpress.com</link>
	<description>Tony Hasler's light hearted approach to learning about Oracle</description>
	<lastBuildDate>Mon, 26 Dec 2011 17:56:04 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='tonyhasler.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://0.gravatar.com/blavatar/cdeedcf106504be2f920ddff94f4c1ab?s=96&#038;d=http%3A%2F%2Fs2.wp.com%2Fi%2Fbuttonw-com.png</url>
		<title>Tony's Oracle Tips</title>
		<link>http://tonyhasler.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://tonyhasler.wordpress.com/osd.xml" title="Tony&#039;s Oracle Tips" />
	<atom:link rel='hub' href='http://tonyhasler.wordpress.com/?pushpress=hub'/>
		<item>
		<title>FORCE_MATCH for Stored Outlines and/or SQL Baselines????? &#8211; follow up</title>
		<link>http://tonyhasler.wordpress.com/2011/12/26/force_match-for-stored-outlines-andor-sql-baselines-follow-up/</link>
		<comments>http://tonyhasler.wordpress.com/2011/12/26/force_match-for-stored-outlines-andor-sql-baselines-follow-up/#comments</comments>
		<pubDate>Mon, 26 Dec 2011 13:45:37 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=488</guid>
		<description><![CDATA[As has happened a couple of times recently, comments on a blog of mine have helped me understand the subject matter on which I am commenting far better. In this case a dialogue with Dom Brooks on my previous blog on how to simulate a stored outline/SQL baseline with FORCE_MATCH using SQL profiles has provided [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=488&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>As has happened a couple of times recently, comments on a blog of mine have helped me understand the subject matter on which I am commenting far better.</p>
<p>In this case a dialogue with <a href="http://orastory.wordpress.com/">Dom Brooks</a> on my <a href="http://tonyhasler.wordpress.com/2011/12/16/force_match-for-stored-outlines-andor-sql-baselines/">previous blog</a> on how to simulate a stored outline/SQL baseline with FORCE_MATCH using SQL profiles has provided me with a simplified way of approaching the creation of said profiles.</p>
<p>The observation is that there is a column OTHER_XML that appears in a number of tables and views from which you can directly copy the outline hints necessary to fix the execution plan of a statement &#8211; and use FORCE_MATCH to boot.</p>
<p>It seems that we are not the first to experiment with this technique.  See for example <a href="http://kerryosborne.oracle-guy.com/2009/11/fixing-bad-index-hints-in-sql-profiles-automatically/"> this blog by Kerry Osborne</a> published a while ago but I think these posts have slightly different emphasis.</p>
<p>The OTHER_XML column appears in PLAN_TABLE/SYS.PLAN_TABLE$, V$SQL_PLAN, and DBA_HIST_SQL_PLAN.  The outline hints from these views are often displayed using the OUTLINE or ADVANCED formatting options to DBMS_XPLAN.DISPLAY, DBMS_XPLAN.DISPLAY_CURSOR, or DBMS_XPLAN.DISPLAY_AWR table functions respectively.</p>
<p>There is also an OTHER_XML column in DBA_SQLTUNE_PLANS that provides an alternative way to accept a SQL profile.  Let me demonstrate.  Let us setup some tables first:<pre class="brush: plain;">

-- 
-- Setup tables for test and gather stats 
-- 
DROP TABLE t1; 
DROP TABLE t2; 
DROP TABLE t3; 

BEGIN 
   FOR r IN (SELECT name FROM dba_sql_profiles) 
   LOOP 
      DBMS_SQLTUNE.drop_sql_profile (r.name); 
   END LOOP; 
END; 
/ 

CREATE TABLE t1 
AS 
       SELECT ROWNUM * 2 c1 
         FROM DUAL 
   CONNECT BY LEVEL &lt;= 100000; 

CREATE TABLE t2 
AS 
       SELECT ROWNUM * 2 - 1 c1 
         FROM DUAL 
   CONNECT BY LEVEL &lt;= 50000 
   UNION ALL 
   SELECT 2 FROM DUAL; 

CREATE TABLE t3 
AS 
       SELECT ROWNUM * 2 c1 
         FROM DUAL 
   CONNECT BY LEVEL &lt;= 200000; 

EXEC dbms_stats.gather_table_stats(user,'T1'); 
EXEC dbms_stats.gather_table_stats(user,'T2'); 
EXEC dbms_stats.gather_table_stats(user,'T3'); 

CREATE INDEX t3_i1 
   ON t3 (c1); 
</pre> </p>
<p>These tables are setup in way to deliberately confuse the CBO.  Now, let us run the SQL Tuning advisor.<pre class="brush: plain;"> 
BEGIN 
   DBMS_SQLTUNE.drop_sql_profile (name =&gt; 'Tony''s SQLTUNE profile'); 
EXCEPTION 
   WHEN OTHERS 
   THEN 
      NULL; 
END; 
/ 

VARIABLE task_name VARCHAR2(20) 

BEGIN 
   :task_name := 
      DBMS_SQLTUNE.create_tuning_task ( 
         sql_text =&gt; ' 
SELECT * 
        FROM t1, t2, t3 
       WHERE t1.c1 = t2.c1 AND t1.c1 = t3.c1 
 '); 
   DBMS_SQLTUNE.execute_tuning_task (:task_name); 
END; 
/ 

SET LONG 100000 LONGC 100000 PAGES 10000 

SELECT DBMS_SQLTUNE.report_tuning_task (:task_name) FROM DUAL 
/ 

-- 
-- This is the standard way to accept a profile (with FORCE_MATCHING) 
-- 

--BEGIN 
--   DBMS_SQLTUNE.accept_sql_profile (task_name =&gt; :task_name, REPLACE =&gt; TRUE, name =&gt; 'Tony''s SQLTUNE profile', force_match =&gt; TRUE);

--END; 
--/ 

--EXPLAIN PLAN 
--   FOR 
--      SELECT * 
--        FROM t1, t2, t3 
--       WHERE t1.c1 = t2.c1 AND t1.c1 = t3.c1; 

--SELECT * FROM TABLE (DBMS_XPLAN.display); 
</pre><br />
The hints stored in the SQL profile by the commented-out method are not guaranteed to (and are not intended to) fix the SQL profile in quite the same way as a stored outline or SQL baseline.  By extracting the outline hints from the advisor task, however, we can fix the plan with the same level of certainty as a SQL baseline or stored outline.</p>
<p><pre class="brush: plain;"> 

DECLARE 
   v_sqlprof_xml   CLOB; 
BEGIN 
   SELECT REGEXP_SUBSTR (other_xml, '&lt;outline_data&gt;.*&lt;/outline_data&gt;') 
     INTO v_sqlprof_xml 
     FROM dba_sqltune_plans JOIN dba_advisor_tasks USING (task_id) 
    WHERE     attribute = 'Using SQL profile' 
          AND other_xml IS NOT NULL 
          AND task_name = :task_name; 

   DBMS_SQLTUNE.import_sql_profile (name =&gt; 'Tony''s SQLTUNE profile' 
                                    , description =&gt; 'Example profile from Tony Hasler''s blog' 
                                    , category =&gt; 'DEFAULT' 
                                    , sql_text =&gt; q'[ 
SELECT * 
        FROM t1, t2, t3 
       WHERE t1.c1 = t2.c1 AND t1.c1 = t3.c1 
]' 
                                    , REPLACE =&gt; TRUE, 
                                    force_match =&gt; TRUE, 
                                    profile_xml =&gt; v_sqlprof_xml); 
END; 
/ 

EXPLAIN PLAN 
   FOR 
      SELECT * 
        FROM t1, t2, t3 
       WHERE t1.c1 = t2.c1 AND t1.c1 = t3.c1; 

SELECT * FROM TABLE (DBMS_XPLAN.display); 
</pre><br />
Note that REGEXP_SUBSTR seems adequate for extracting the outline hints from the OTHER_XML column.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/488/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/488/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/488/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=488&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/12/26/force_match-for-stored-outlines-andor-sql-baselines-follow-up/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>FORCE_MATCH for Stored Outlines and/or SQL Baselines?????</title>
		<link>http://tonyhasler.wordpress.com/2011/12/16/force_match-for-stored-outlines-andor-sql-baselines/</link>
		<comments>http://tonyhasler.wordpress.com/2011/12/16/force_match-for-stored-outlines-andor-sql-baselines/#comments</comments>
		<pubDate>Fri, 16 Dec 2011 14:11:38 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=468</guid>
		<description><![CDATA[Note that there is a follow up to this post here that you should read after this post. Stored outlines were introduced in Oracle 9i as a way of helping stabilise execution plans.  In 11gR1 these are deprecated in favour of enterprise-edition-only SQL Baselines (sorry standard edition users )-:) but as of 11gR2 neither facility [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=468&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><i><b>Note that there is a follow up to this post <a href="http://tonyhasler.wordpress.com/2011/12/16/force_match-fo…-sql-baselines/">here</a> that you should read after this post.</b></i></p>
<p>Stored outlines were introduced in Oracle 9i as a way of helping stabilise execution plans.  In 11gR1 these are deprecated in favour of enterprise-edition-only SQL Baselines (sorry standard edition users )-:) but as of 11gR2 neither facility has the capability built into SQL profiles for matching a plan to any statement that matches text when literal values and whitespace are ignored.  I heard this raised as an enhancement request at last week&#8217;s Uk Oracle User Group conference.</p>
<p>By a strange coincidence, I was faced with this precise problem this week.  The issue arose with a large application that was very time consuming and expensive to regression test.  It involved a dynamically generated SQL statement against partitioned tables that avoided the use of bind variables for literals, including a couple of dates.  Apart from the literal values, the query was identical from call to call.</p>
<p>When historic data was queried, the execution plan was fine.  However, when the current day was queried the plan was often very poor.  Neither gathering statistics during the day nor copying partition statistics from one day to the next helped.</p>
<p>My long term plan is to identify why the statistics copying isn&#8217;t working well and either to customise the copy process or to abandon the use of partition level statistics altogether.  But that is root cause analysis.  What about a recovery plan for my client?</p>
<p>The funny thing is that SQL baselines, stored outlines, and SQL profiles all store hints.  Normally, the types of hints that are stored in a SQL profile are very different from those stored in a SQL baseline or in a stored outline but they don’t have to be.</p>
<p>Let me demonstrate with a two table join for Christmas Day:</p>
<pre><code>CREATE TABLE t1 (d1, c1 DEFAULT 'x')
PARTITION BY RANGE
   (d1)
   (
      PARTITION p1 VALUES LESS THAN (DATE '2012-01-01'),
      PARTITION pdefault VALUES LESS THAN (maxvalue))
AS
       SELECT DATE '2011-12-25', RPAD ('x', 2000)
         FROM DUAL
   CONNECT BY LEVEL &lt;= 10000; 

CREATE TABLE t2
AS
       SELECT DATE '2011-12-25' d1
         FROM DUAL
   CONNECT BY LEVEL &lt;= 100; 

EXEC dbms_stats.gather_table_stats(ownname=&gt;user,tabname=&gt;'T1',no_invalidate=&gt;false);
EXEC dbms_stats.gather_table_stats(ownname=&gt;user,tabname=&gt;'T2',no_invalidate=&gt;false); 

EXPLAIN PLAN
   FOR
      SELECT *
        FROM t1 NATURAL JOIN t2
       WHERE d1 = DATE '2011-12-25'; 

SET PAGES 0 LINES 132 

SELECT * FROM TABLE (DBMS_XPLAN.display (NULL, NULL, 'outline'));
</code></pre>
<p>This is the output I get on my 11.2.0.1 test database:
<pre><code>Plan hash value: 937381588             

----------------------------------------------------------------------------------------------------
| Id  | Operation                | Name    | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT         |         |  1000K|  1923M|   932   (1)| 00:00:12 |       |       |
|   1 |  HASH JOIN               |         |  1000K|  1923M|   932   (1)| 00:00:12 |       |       |
|   2 |   PART JOIN FILTER CREATE| :BF0000 |   100 |   800 |     3   (0)| 00:00:01 |       |       |
|   3 |    TABLE ACCESS FULL     | T2      |   100 |   800 |     3   (0)| 00:00:01 |       |       |
|   4 |   PARTITION RANGE SINGLE |         | 10000 |    19M|   923   (1)| 00:00:12 |KEY(AP)|KEY(AP)|
|   5 |    TABLE ACCESS FULL     | T1      | 10000 |    19M|   923   (1)| 00:00:12 |     1 |     1 |
---------------------------------------------------------------------------------------------------- 

Outline Data
-------------                          

  /*+
      BEGIN_OUTLINE_DATA
      USE_HASH(@"SEL$58A6D7F6" "T1"@"SEL$1")
      LEADING(@"SEL$58A6D7F6" "T2"@"SEL$1" "T1"@"SEL$1")
      FULL(@"SEL$58A6D7F6" "T1"@"SEL$1")
      FULL(@"SEL$58A6D7F6" "T2"@"SEL$1")
      OUTLINE(@"SEL$1")
      OUTLINE(@"SEL$2")
      MERGE(@"SEL$1")
      OUTLINE_LEAF(@"SEL$58A6D7F6")
      ALL_ROWS
      DB_VERSION('11.2.0.1')
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      IGNORE_OPTIM_EMBEDDED_HINTS
      END_OUTLINE_DATA
  */</code></pre>
<p>Now what if I re-gather statistics and issue the same statement for New Years day?
<pre><code>EXEC dbms_stats.gather_table_stats(ownname=&gt;user,tabname=&gt;'T1',no_invalidate=&gt;false);
EXEC dbms_stats.gather_table_stats(ownname=&gt;user,tabname=&gt;'T2',no_invalidate=&gt;false);
EXPLAIN PLAN
   FOR
      SELECT *
        FROM t1 NATURAL JOIN t2
       WHERE d1 = DATE '2012-01-01'; 

SELECT * FROM TABLE (DBMS_XPLAN.display (NULL, NULL, 'outline -predicate'));</code></pre>
<p>This is the plan for that date:
<pre><code>Plan hash value: 2331183230
------------------------------------------------------------------------------------------------
| Id  | Operation               | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT        |      |     1 |  1019 |     6  (17)| 00:00:01 |       |       |
|   1 |  HASH JOIN              |      |     1 |  1019 |     6  (17)| 00:00:01 |       |       |
|   2 |   PARTITION RANGE SINGLE|      |     1 |  1011 |     2   (0)| 00:00:01 |     2 |     2 |
|   3 |    TABLE ACCESS FULL    | T1   |     1 |  1011 |     2   (0)| 00:00:01 |     2 |     2 |
|   4 |   TABLE ACCESS FULL     | T2   |     1 |     8 |     3   (0)| 00:00:01 |       |       |
------------------------------------------------------------------------------------------------
Outline Data
-------------
  /*+
      BEGIN_OUTLINE_DATA
      USE_HASH(@"SEL$58A6D7F6" "T2"@"SEL$1")
      LEADING(@"SEL$58A6D7F6" "T1"@"SEL$1" "T2"@"SEL$1")
      FULL(@"SEL$58A6D7F6" "T2"@"SEL$1")
      FULL(@"SEL$58A6D7F6" "T1"@"SEL$1")
      OUTLINE(@"SEL$1")
      OUTLINE(@"SEL$2")
      MERGE(@"SEL$1")
      OUTLINE_LEAF(@"SEL$58A6D7F6")
      ALL_ROWS
      DB_VERSION('11.2.0.1')
      OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
      IGNORE_OPTIM_EMBEDDED_HINTS
      END_OUTLINE_DATA
  */</code></pre>
<p>Now what if I know that the first plan is desirable and the second one isn’t?  Well, I can load the outline hints associated with the good plan into a SQL profile:
<pre><code>BEGIN
   DBMS_SQLTUNE.import_sql_profile (
      name          =&gt; 'BLOG profile',
      description   =&gt; 'Example profile from Tony Hasler''s blog',
      category      =&gt; 'DEFAULT',
      sql_text      =&gt; q'[
 SELECT *
        FROM t1 NATURAL JOIN t2
       WHERE d1 = DATE '2011-12-25'
   ]',
      REPLACE       =&gt; TRUE,
      force_match   =&gt; TRUE,
      profile       =&gt; sqlprof_attr('BEGIN_OUTLINE_DATA',
'USE_HASH(@"SEL$58A6D7F6" "T1"@"SEL$1")',
'LEADING(@"SEL$58A6D7F6" "T2"@"SEL$1" "T1"@"SEL$1")',
'FULL(@"SEL$58A6D7F6" "T1"@"SEL$1")',
'FULL(@"SEL$58A6D7F6" "T2"@"SEL$1")',
'OUTLINE(@"SEL$1")',
'OUTLINE(@"SEL$2")',
'MERGE(@"SEL$1")',
'OUTLINE_LEAF(@"SEL$58A6D7F6")',
'ALL_ROWS',
'DB_VERSION(''11.2.0.1'')',
'OPTIMIZER_FEATURES_ENABLE(''11.2.0.1'')',
'IGNORE_OPTIM_EMBEDDED_HINTS',
'END_OUTLINE_DATA'));
END;</code></pre>
<p>Careful editing is required: each hint has to be an element in the SQLPROF_ATTR array so be careful with multi-line hints.  If you have a very long hint you may have to use the overloaded variant of this undocumented routine that allows you to specify XML rather than an array (profile_xml rather than profile).  In my case, I was able to simply replace a long list of columns in an index hint with an index name.  Also note the need to deal with sinqle quotes inside the strings (as in OPTIMIZER_FEATURES_ENABLE).</p>
<p>Note the important FORCE_MATCH parameter. </p>
<p>With this profile in place, we get the same plan for both Christmas Day and New Years Day.</p>
<p>You may wonder what happens if the SQL text already contains hints?  Well, you have to include these embedded hints in the SQL text when you create the profile but the BEGIN_OUTLINE_DATA, IGNORE_EMBEDDED_HINTS, and END_OUTLINE_DATA hints in the SQL profile combine to allow the embedded hints to be overridden.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/468/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/468/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/468/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=468&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/12/16/force_match-for-stored-outlines-andor-sql-baselines/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>ACID and TPC-C</title>
		<link>http://tonyhasler.wordpress.com/2011/08/24/acid-and-tpc-c/</link>
		<comments>http://tonyhasler.wordpress.com/2011/08/24/acid-and-tpc-c/#comments</comments>
		<pubDate>Wed, 24 Aug 2011 19:53:20 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[ACID]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=455</guid>
		<description><![CDATA[I saw Tom Kyte yesterday and he stated that in his opinion the ACID issue had no bearing on TPC-C tests.  Well, I beg to differ.  The current specification can be viewed in full here but here are the three key parts of the specification. First from section 3.4 on isolation requirements: P1 (&#8220;Dirty Read&#8221;): Database transaction [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=455&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I saw Tom Kyte yesterday and he stated that in his opinion <a href="http://tonyhasler.wordpress.com/2011/08/22/why-is-acid-important/">the ACID issue</a> had no bearing on TPC-C tests.  Well, I beg to differ.  The current specification can be viewed in full <a href="http://www.tpc.org/tpcc/spec/tpcc_current.pdf">here</a> but here are the three key parts of the specification.</p>
<p>First from section 3.4 on isolation requirements:</p>
<p><em>P1 (&#8220;Dirty Read&#8221;): Database transaction T1 modifies a data element. Database transaction T2 then reads that data element before T1 performs a COMMIT. If T1 were to perform a ROLLBACK, T2 will have read a value that was never committed and that may thus be considered to have never existed.</em></p>
<p>A little ater on it says that dirty reads are not allowed, as you might expect.</p>
<table dir="ltr" width="500" border="1" cellspacing="0" cellpadding="9">
<tbody>
<tr>
<td valign="top" width="20%" height="17">Isolation Level</td>
<td valign="top" width="20%" height="17">P0</td>
<td valign="top" width="20%" height="17">P1</td>
<td valign="top" width="20%" height="17">P2</td>
<td valign="top" width="20%" height="17">P3</td>
</tr>
<tr>
<td valign="top" width="20%" height="7">0</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
</tr>
<tr>
<td valign="top" width="20%" height="7">1</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
</tr>
<tr>
<td valign="top" width="20%" height="7">2</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Possible</td>
</tr>
<tr>
<td valign="top" width="20%" height="7">3</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
<td valign="top" width="20%" height="7">Not Possible</td>
</tr>
</tbody>
</table>
<p>skipping ahead a bit&#8230;</p>
<table dir="ltr" width="500" border="1" cellspacing="0" cellpadding="9">
<tbody>
<tr>
<td valign="top" width="20%" height="38">2.</td>
<td valign="top" width="20%" height="38">{Ti, Tn}<span style="font-size:x-small;"><span style="font-size:xx-small;">1 ≤ i ≤ 4 </span></span></td>
<td valign="top" width="20%" height="38">P0, P1, P2</td>
<td valign="top" width="20%" height="38">Ti</td>
<td valign="top" width="20%" height="38">Level 2 isolation for New-Order, Payment, Delivery, and Order-Status transactions relative to any arbitrary transaction.</td>
</tr>
</tbody>
</table>
<p>Now, in section 3.5.2 we have the committed property definition:</p>
<p><em>A transaction is considered committed when the transaction manager component of the system has either written the log or written the data for the committed updates associated with the transaction to a durable medium.</em></p>
<p>Therefore, Oracle violates the isolation property because data not yet committed has been made visible to other transactions and &#8220;Dirty Reads&#8221; are not prevented.  Specifically, the read-only order status transaction may see changes made by the other read-write transactions listed above.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/455/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/455/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/455/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=455&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/08/24/acid-and-tpc-c/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>Why is ACID important?</title>
		<link>http://tonyhasler.wordpress.com/2011/08/22/why-is-acid-important/</link>
		<comments>http://tonyhasler.wordpress.com/2011/08/22/why-is-acid-important/#comments</comments>
		<pubDate>Mon, 22 Aug 2011 09:34:52 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[ACID]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=450</guid>
		<description><![CDATA[As the updated version of my last post stated, what Jonathan Lewis described as a &#8220;truly stunning observation&#8221; was uncovered: depending on your point of view, Oracle violates either the &#8220;I&#8221; in ACID or the &#8220;D&#8221;.  I won&#8217;t repeat the technical description of the issue.  This is already covered  here and here. The purpose of this [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=450&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>As the updated version of my last post stated, what Jonathan Lewis described as a &#8220;truly stunning observation&#8221; was uncovered: depending on your point of view, Oracle violates either the &#8220;I&#8221; in ACID or the &#8220;D&#8221;.  I won&#8217;t repeat the technical description of the issue.  This is already covered <br />
<a href="http://tonyhasler.wordpress.com/2011/08/04/log-file-sync-and-log-file-parallel-write-part-3/#comments">here</a> and <a href="http://jonathanlewis.wordpress.com/2011/08/19/redo-2/">here</a>.</p>
<p>The purpose of this blog entry is to explain why this is such a critical issue.  There are three main areas I want to touch on (in increasing order of importance):</p>
<p>- Giving humans invalid data<br />
- Giving computers invalid data<br />
- Legal and reputational issues</p>
<p>Let us begin with the human case.  You call your broker late at night with instructions:  &#8220;I hear that Oracle is not ACID.  Please sell my 10 million pounds worth of Oracle stock!&#8221;.  Your broker says, &#8220;OK, I have submitted your instructions for processing.  It might take a few minutes&#8221;.  You say&#8230;&#8221;I&#8217;ll hold.  I am going on vacation tomorrow and want to make sure that this is done before I leave&#8221;.  Your broker replies: &#8220;Let me query the system to check&#8230;&#8230;oh yes your instructions are definitely in our system.  Your transaction to sell Oracle at best will be actioned tomorrow morning&#8221;.  You feel relieved.  However, the system crashed after the broker was sent confirmation that your transaction was processed but before the transaction was on disk.  Your request to sell Oracle stock is lost and you return from vacation to find that no record exists of your request.</p>
<p>This sort of scenario has probably happened a few times as a result of Oracle&#8217;s lack of ACID.  However, nobody would have known why.  On the other hand, it will be a very rare event.  Even an entire call centre will only make at most a few queries per second and most of these will be for data that was committed long ago.  It will be a rare event that sees queried data lost on a crash.</p>
<p>If this sort of situation has ever happened to you it is highly unlikely that Oracle&#8217;s lack of ACID was the cause.  Bugs in application code, media recovery and human error amongst other causes would account for the vast majority of such mishaps.</p>
<p>However, let us turn our attention to computer to computer communication.  Consider the following situation.  You process some kind of incoming data and place the data in a staging table.  Let us assume that the table is partitioned by hash into 256 segments. You have 256 producer threads that:</p>
<p>- read data from their own partition<br />
- enrich it in a few ways (including adding a primary key generated from a sequence)<br />
- send the enriched data to an AQ queue<br />
- delete the data from the staging table<br />
- commit.<br />
- loop</p>
<p>Meanwhile, you have another group of sessions that read messages from the AQ queue and send data to a remote system that you have no control over.  Your consumer threads:</p>
<p>- Destructively read a message from the AQ queue<br />
- Insert the data into the remote system (using the remote systems transaction system)<br />
- Commit the remote transaction<br />
- Commit the local transaction.<br />
- loop</p>
<p>You are a smart guy or gal and realise that there is an issue with your code:  the system may crash after the remote transaction has committed and before the local one does.  This means that after a recovery the message will be back in AQ and will be sent again.  However, there is a unique primary key in the AQ message and the remote system is built in such a way as to detect these &#8220;possible duplicates&#8221; and discards the message when it is resent.</p>
<p>Wonderful, but because of the failure of ACID it is possible that not only the uncommitted consumers transaction is lost on a crash but also the &#8220;committed&#8221; producers transaction!  Now on recovery, the message would not be in the AQ queue but back in the staging table.  Now when the message is resent it will have a brand new primary key and the remote system will not detect the duplicate.</p>
<p>There are lots of systems where the log writer is the bottleneck, perhaps because of replication to a DR site or for other reasons.  In these sorts of heavily loaded systems it is virtually guaranteed that at the time of a crash there will be some transactions that are sitting the log buffer waiting to be written out to disk.  When automated tasks are processing transactions immediately after that have been committed recovery will create a problem nearly all the time.</p>
<p>What is so bad about this situation is that there is no easy way, short of a complete redesign, to fix the application to workaround this problem as Oracle provides no way for a session (other than the committing one) to wait for the data it is seeing to be on disk.  Sure, the consumer threads could wait a few seconds before sending the data to the remote host but this would kill performance.</p>
<p>It is the potentially high rate that consumer threads can read &#8220;undurable data&#8221; that makes the probability of an error high in these situations.</p>
<p>The final, and most important, area is the potential legal and reputation issues that this topic raises.  It will be very difficult for Oracle to claim that this is a documentation error and amend same to say that Oracle is NOT ACID.  On the other hand, it is very difficult to see how they can treat this as a simple &#8220;bug&#8221; as it is a fundamental part of the design of the product and any &#8220;bug fix&#8221; would seem to have massive performance implications.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/450/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/450/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/450/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=450&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/08/22/why-is-acid-important/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>&#8216;log file sync&#8217; and &#8216;log file parallel write&#8217; &#8211; part 3</title>
		<link>http://tonyhasler.wordpress.com/2011/08/04/log-file-sync-and-log-file-parallel-write-part-3/</link>
		<comments>http://tonyhasler.wordpress.com/2011/08/04/log-file-sync-and-log-file-parallel-write-part-3/#comments</comments>
		<pubDate>Thu, 04 Aug 2011 17:25:21 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[ACID]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=435</guid>
		<description><![CDATA[In the third and final part of this series on &#8216;log file sync&#8217; and &#8216;log file parallel write&#8217; (see here for part 1 and part 2) I want to talk about the difference between a commit statement issued from PL/SQL and a commit statement issued from SQL. When I first wrote this blog the intention [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=435&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>In the third and final part of this series on &#8216;log file sync&#8217; and &#8216;log file parallel write&#8217; (see here for <a href="http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-1/">part 1</a> and <a href="http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-2/">part 2</a>) I want to talk about the difference between a commit statement issued from PL/SQL and a commit statement issued from SQL.</p>
<p><strong>When I first wrote this blog the intention was to discuss the few circumstances when a COMMIT from a PL/SQL might impact ACID properties and to explain why 99% of the time it didn&#8217;t.  At the time I wrote this blog the assumption was that ACID properties could confidently be assigend to standard COMMIT statements executed from SQL (e.g. SQL*PLUS, JDBC, etc.).  As you will see in the comments to this blog this is far from the case.  I have annoted the false assumption in bold.</strong></p>
<p>The first thing to get your head round is that PL/SQL language statements are different from SQL statements. When you issue a COMMIT statement from SQL*PLUS, from Java, C, or almost all other languages you need to refer to the &#8220;<strong>SQL Language Reference</strong>&#8221; manual for syntax and semantics. When you issue a COMMIT statement (or one of the other seven SQL statements with direct PL/SQL equivalents) from PL/SQL, however, you need to look in &#8220;<strong>PL/SQL Language Reference</strong>&#8221; manual for syntax and semantics. Sure, they do very similar things but there are subtle differences.</p>
<p>In the 11.2 SQL Language Reference manual, you will see this statement:</p>
<p><em>If you omit [the write clause], then the behavior of the commit operation is controlled by the COMMIT_LOGGING and COMMIT_WAIT initialization parameters, if they have been set.</em></p>
<p>The COMMIT_LOGGING and COMMIT_WAIT parameters replace the COMMIT_WRITE parameter used in previous releases. Unfortunately, the documentation for COMMIT_WRITE is at best incomplete and at worst wrong. To see what the default semantics are we need to look at the documentation for the deprecated COMMIT_WRITE parameter. Here we can see:</p>
<p><em>If this parameter is not explicitly specified, then database commit behaviour defaults to writing commit records to disk before control is returned to the client.</em></p>
<p>We also see this line:</p>
<p><em>Be aware that the NOWAIT option can cause a failure that occurs after the database receives the commit message, but before the redo log records are written. This can falsely indicate to a transaction that its changes are persistent. Also, it can violate the durability of ACID (Atomicity, Consistency, Isolation, Durability) transactions if the database shuts down unexpectedly.</em></p>
<p>This is poor English but obviously sounds scary. Most programmers would and should avoid using NOWAIT in their programs or overriding the default session or system initialisation settings. However, let us look at the documentation in the PL/SQL reference manual:</p>
<p><em>The default PL/SQL commit behaviour for nondistributed transactions is BATCH NOWAIT if the COMMIT_LOGGING and COMMIT_WAIT database initialization parameters have not been set.</em></p>
<p>Whoa! Suddenly the behaviour that you were warned might cause a violation of the durability of durability (that is what it says isn&#8217;t it?) is now the default behaviour! Why is something that is so dangerous from SQL suddenly safe from PL/SQL and why?</p>
<p>Well, as we shall see it isn&#8217;t always safe. But to understand what is safe and what is not safe let me try to expand on the scary documentation from the SQL language reference manual. Consider this piece of pseudo-code from some non-PL/SQL program:</p>
<pre>   do some DML;
   commit;
   put_on_screen('Transaction complete') ;</pre>
<p>Imagine that the commit returned immediately without waiting for the data to be recorded on disk. You might tell the user that the transaction committed and then the database might crash causing all the DML to be lost. You have misled the user. What if, however, the &#8216;user&#8217; was another computer system or database?</p>
<pre>    get a message from a remote system;
    do some DML;
    commit;
    send confirmation of receipt of message to remote system;</pre>
<p>Well, a human might (or might not) realise that there was a crash around the time his message was displayed saying that his transaction completed but a remote computer system will not be so paranoid. It is highly unlikely that such a remote application would retransmit a message if it receives an explicit positive acknowledgement to the first transmission.</p>
<p>So this is why the default behaviour for SQL is to wait until the transaction details are recorded in the redo log on disk; it is to ensure that if the application relays the fact that the transaction is committed to a third party (human or computer) that the transaction isn&#8217;t later discovered to be &#8216;uncommitted&#8217; so to speak. This is the &#8220;durability&#8221; part of ACID.</p>
<p>Now that we have that sorted out let us turn our attention to PL/SQL. First of all, the documentation here is &#8220;incomplete&#8221;. Let me try and expand:</p>
<ul>
<li><strong>For all but the last commit statement executed before PL/SQL returns control to the caller</strong> the default PL/SQL commit behaviour for nondistributed transactions is BATCH NOWAIT if the COMMIT_LOGGING and COMMIT_WAIT database initialization parameters have not been set.</li>
<li>The default behaviour for the last commit statement executed before PL/SQL returns control to the caller is to trigger a wait at the point control is returned to the caller.</li>
<li>Sometimes you will experience a &#8216;log file sync&#8217; wait event even if you explicitly or implicitly specify NOWAIT.</li>
</ul>
<p>Ok, so the semantics are not only apparently dangerous but also complicated. Why?</p>
<p>Well let us take an example PL/SQL block and add some measurements of <strong>&#8216;log file sync&#8217;</strong>wait events.  Let us begin by creating a test table:</p>
<pre>CREATE TABLE T1 (dummy varchar2(100)) ;</pre>
<p>Now our test:</p>
<pre> SELECT total_waits
  FROM v$session_event
 WHERE sid = USERENV ('SID') AND event = 'log file sync';

BEGIN
   INSERT INTO t1 (dummy)
        VALUES ('Transaction 1');

   COMMIT;

   INSERT INTO t1 (dummy)
        VALUES ('Transaction 2');

   COMMIT;
END;

SELECT total_waits
  FROM v$session_event
 WHERE sid = USERENV ('SID') AND event = 'log file sync';</pre>
<p>We see only one <strong>&#8216;log file sync&#8217;.</strong> This is the one triggered at the end of the block by the second commit. The first commit triggered no wait. However, no risk of a consistency problem arises. This is because:</p>
<ul>
<li>Locking will prevent either inserted row being visible to outside sessions until the redo is actually on disk and</li>
</ul>
<p><strong>The statement above is how databases in general are supposed to work.  Oracle does not work this way, however: commits from PL/SQL or SQL are visible to other sessions before they are actually on disk.  See the comments below for more discussion.</strong></p>
<ul>
<li>The second transaction will have a higher SCN than the first and in the event of a crash Oracle will ensure that if the first transaction needs to be rolled back the second will be also.</li>
</ul>
<p>This can be a bit tricky to get your head round but it is a legitimate optimisation that I believe (historians are welcome to correct me) was first introduced in IMS Fastpath many decades ago and has been copied by Oracle and other database systems subsequently.</p>
<p>When I have explained this to other DBAs and programmers over the years I have known when the point has been understood when I get the question:</p>
<p><em>&#8220;Yes, but what if I communicate the commit outside of the PL/SQL block by some means other than updating the database&#8221;?</em></p>
<p>This question indicates a grasp of the key concept. Unfortunately, I have always answered this question incorrectly! Let us get one easy case out of the way first:</p>
<pre>BEGIN
   INSERT INTO t1 (dummy)
        VALUES ('Transaction 1');

   COMMIT;
   DBMS_OUTPUT.put_line ('Transaction committed');
END;</pre>
<p>This is actually safe because the results of DBMS_OUTPUT.PUT_LINE are invisible until after the PL/SQL block returns. So if the supposedly committed transaction is subsequently rolled back nobody will ever have known that the commit statement returned to the PL/SQL block in the first place.</p>
<p>But what about this:</p>
<pre>SELECT total_waits
  FROM v$session_event
 WHERE sid = USERENV ('SID') AND event = 'log file sync';

DECLARE
   filehandler   UTL_FILE.file_type;
BEGIN
   INSERT INTO t1 (dummy)
        VALUES ('Transaction 1');

   COMMIT;

   INSERT INTO t1@dblink (dummy)
        VALUES ('Transaction 2');

   COMMIT;
   filehandler :=
      UTL_FILE.fopen (
                      'DATA_PUMP_DIR',
                      'test_file.txt',
                      'W'
                     );
   UTL_FILE.putf (
                  filehandler,
                  'A transaction committed\n'
                 );
   UTL_FILE.fclose (filehandler);

   INSERT INTO t1 (dummy)
        VALUES ('Transaction 3');

   COMMIT;
END;

SELECT total_waits
  FROM v$session_event
 WHERE sid = USERENV ('SID') AND event = 'log file sync';</pre>
<p>You might have to run this a couple of times as there is sometimes an &#8216;extra&#8217; <strong>&#8216;log file sync&#8217;</strong> wait event but most of the time there will only be one wait for the entire PL/SQL block. The use of a database link doesn&#8217;t mean there is a distributed transaction because there are no changes to the local database in the transaction. If the local database crashes neither the update to the text file nor the update to the remote database will be &#8220;rolled back&#8221; so an inconsistency can arise after all.</p>
<p>Until I started to prepare for writing this blog I had always assumed that whenever a remote database was updated or a package such as UTL_FILE or UTL_HTTP was called then a <strong>&#8216;log file sync&#8217;</strong> would occur at that point. This is such a fundamental requirement of database consistency, it never occurred to me to check. Not only that, but the PL/SQL engine knows enough to do this easily. However, even in 11.2 this is not the case. You can protect against these inconsistencies by changing your PL/SQL COMMIT statements (the last being optional) to &#8216;COMMIT WORK WRITE WAIT&#8217; statements.</p>
<p>So when is it safe to use COMMIT WORK WRITE NOWAIT (either explicitly or not)?</p>
<ul>
<li>If the only thing you do between &#8216;commit number one&#8217; and &#8217;commit number two&#8217; is issue queries and/or updates to the local database it is safe for &#8216;commit number one&#8217; to use NOWAIT. This is true both for PL/SQL and SQL.</li>
<li>If you commit a transaction and then your program terminates completely then it is safe to use NOWAIT. This is true for both SQL and PL/SQL.</li>
<li>If you commit a transaction and then &#8216;communicate with the outside world&#8217; then you should wait (explicitly in PL/SQL) if you care about consistency.</li>
</ul>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/435/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/435/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/435/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=435&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/08/04/log-file-sync-and-log-file-parallel-write-part-3/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>&#8216;log file sync&#8217; and &#8216;log file parallel write&#8217; &#8211; part 2</title>
		<link>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-2/</link>
		<comments>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-2/#comments</comments>
		<pubDate>Sun, 24 Jul 2011 20:24:05 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=428</guid>
		<description><![CDATA[I mentioned at the very end of part 1 that ‘log file parallel write’ wait times are in fact unlikely to be close to ‘log file sync’ wait times.  There have been many questions and blog entries on this topic over the years.  One common starting point seems to be Metalink article 34592.1.  Another good article [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=428&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I mentioned at the very end of <a href="http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-1/">part 1</a> that <strong>‘log file parallel write’</strong> wait times are in fact unlikely to be close to ‘<strong>log file sync’</strong> wait times.  There have been many questions and blog entries on this topic over the years.  One common starting point seems to be Metalink article 34592.1.  Another good article is this one:</p>
<p><a href="http://kevinclosson.wordpress.com/2007/07/21/manly-men-only-use-solid-state-disk-for-redo-logging-lgwr-io-is-simple-but-not-lgwr-processing/">http://kevinclosson.wordpress.com/2007/07/21/manly-men-only-use-solid-state-disk-for-redo-logging-lgwr-io-is-simple-but-not-lgwr-processing/</a>.</p>
<p>However, it seems that everybody has missed a fundamental complication of the changes in commit processing introduced in 10g.  The point has probably been missed because it is normally very hard to demonstrate.</p>
<p>Oracle 9i and below Oracle had the concept of a ‘log buffer’.  You would be in good company if you thought that later versions had a log buffer as well.  After all, there is an initialisation parameter LOG_BUFFER that specifies how big it is.</p>
<p>In reality, Oracle now uses a combination of <strong>‘private redo strands’</strong> and <strong>‘public redo strands’</strong>.</p>
<p>Private redo strands do not exist in instances within a RAC cluster.  In a non-RAC environment each transaction gets its own redo strand (subject to availability).  These strands are used in combination with in-memory undo (IMU) to reduce the amount of data that is logged but more importantly to reduce latching conflicts; data is normally copied from a private redo strand only at commit time.  Data will also be copied out if the private redo strand fills up before a commit is issued.  These private redo strands are allocated from shared pool.</p>
<p>RAC or no RAC there is also a minimum of two <strong>public redo strands</strong> as well.  In larger systems there may be more (number of CPUs divided by 16).  These public redo strands also contribute to latch reduction.  The number of these strands can be changed by use of the hidden parameter <strong>“_log_parallelism_max”.</strong></p>
<p>In relatively quiet systems only one public redo strand may be in use but even with as few as two concurrent sessions busy generating redo you can see the second redo strand being used.  When that happens, LGWR <strong>writes out the data in the public redo strands sequentially</strong>!</p>
<p>How do I know this?</p>
<p>Well, I was performing a rather artificial insert-only performance test on one instance of an 11.2 RAC database on a 64-CPU system with a suspected faulty I/O subsystem (don’t ask).  LGWR was spending 90% of its time in <strong>‘log file parallel write’ (LFPW)</strong> and about 10% of the time on the CPU.  I had taken the advice in Kevin’s article above and “reniced” LGWR so I was fairly sure that LGWR rescheduling was not a big issue: throughput was limited by the I/O subsystem.</p>
<p>Nevertheless,<strong> ‘log file sync’ (LFS)</strong> wait events averaged 4 or 5 times the LFPW times despite the average CPU utilisation only being about 25%.  Theoretically, one would expect that the average LFS time would be somewhat more than 50% higher than LFPW in a system that is limited by LGWR I/O:  if LGWR has just started an I/O at the time that you commit then you have to wait for that I/O to finish as well as your own that follows.  If you are luckier LGWR may just finish an I/O as you commit and now you just have to wait for your own I/O. This gives an average LFS/LFPW ratio of 1.5.  Given the fact that you are still in LFS when LGWR is on the CPU doing its post processing I thought that LFS should be a little higher than 50% above LFPW but certainly not 300% or 400% higher.</p>
<p>Explanations revolving around ‘CPU storms’ didn’t seem plausible so I had to dig deeper.  Well, by ‘digging deeper’ I mean a) posting a question on OTN and asking for help from ‘knowledgeable friends’.  When that failed b) guessing.  I eventually guessed correctly when I set <strong>“_log_parallelism_max”</strong> to 1 (it was previously 4 on my 64 CPU system).</p>
<p>The faulty I/O subsystem had (has) a characteristic that throughput is several times higher when the I/O size is larger.  The reason for this is not yet known.  However, when the initialisation parameter change was made:</p>
<ul>
<li>The average size of the I/O increased several times</li>
<li>The (faulty) I/O subsystem throughput increased several fold</li>
<li>The LFS time came back down to just over 50% of the LFPW time.</li>
<li>LGWR started waiting on the idle event<strong> ‘rdbms ipc message’</strong>; the system was no longer limited by I/O.</li>
<li>Latching increased thus proving that the long term solution was to fix the I/O subsystem not make unsupported changes to initialisation parameters!</li>
</ul>
<p>I then set about trying to devise some kind of experiment that others can repeat to verify my conclusions.  The problem is that the only way to get more than one public redo strand in play is to have sufficient redo being generated that a redo allocation latch collision occurs.  You can easily generate this scenario.  You can also prove that the number of transactions being committed each write (redo size/commit size) is much less than the number of processes in LFS but I can’t think of a way to prove that this discrepancy is caused by I/O serialisation without a faulty I/O subsystem.  Sure you can run a trace and see that there are multiple LFPW events between idle <strong>‘rdbms ipc message’</strong> events but that is hardly conclusive.</p>
<p>Any ideas on how to devise an experiment would be warmly welcomed.  In the meantime I guess you will just have to take my word for it!</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/428/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/428/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/428/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=428&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-2/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>&#8216;Log file sync&#8217; and &#8216;log file parallel write&#8217; &#8211; part 1</title>
		<link>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-1/</link>
		<comments>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-1/#comments</comments>
		<pubDate>Sun, 24 Jul 2011 20:15:12 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=425</guid>
		<description><![CDATA[Preamble This is the first part of a three part blog about LGWR. If you want a thorough understanding of LGWR, ‘log file sync’, and ‘log file parallel write’ read on. If you are an Oak Table member you might want to skip to the more advanced topics in parts 2 and 3 (unless you [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=425&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p><strong>Preamble</strong><br />
This is the first part of a three part blog about LGWR. If you want a thorough understanding of LGWR, <strong>‘log file sync’</strong>, and <strong>‘log file parallel write’</strong> read on. If you are an Oak Table member you might want to skip to the more advanced topics in parts 2 and 3 (unless you want to point out errors and omissions of course <img src='http://s0.wp.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> ).</p>
<p>You are a programmer or a DBA and you are trying for the first time in your life to analyze an Oracle performance problem. What is the most important concept you need to understand to get started?</p>
<p>Well, there are a number of right answers to this question but rightly or wrongly the &#8216;The Oracle wait interface&#8217; would be many people&#8217;s answer. A very basic approach would be to look at the highest wait event on the system and focus on that. So for example, if you see a lot of &#8216;db file sequential read&#8217; wait events you might conclude that faster disks might be the answer. You may be a bit smarter and realise that these events are generally associated with indexed access and try and work out what indexes are being accessed and figure out if they (or the queries that use them) can be redesigned.</p>
<p>There are, of course, numerous books and web articles that explain the deficiencies of this type of approach but in many cases it seems to work. The success of this type of basic approach will be quite high in simple systems where performance has been neglected for a long time and there are one or two ‘quick wins’ that can have a significant benefit. In complex systems, or ones that have been properly managed over the years this sort of basic analysis is unlikely to be enough.</p>
<p>Cary Millsap wrote a book with Jeff Holt many years ago entitled ‘Optimizing Oracle Performance’ in which he explained the limitations of any system wide tuning approach. Even though the book was written many years ago, I would say that this is still mandatory reading for any Oracle performance specialist. The next most important book, in my opinion, is a more modern work by Christian Antognini <a href="http://antognini.ch/top/">http://antognini.ch/top/</a> but I digress.</p>
<p><strong>Is ‘LOG FILE SYNC’ (LFS) relevant?</strong></p>
<p>Arguably the single most misleading wait event in the Oracle database is <strong>‘log file sync’ (LFS).</strong> Let me explain why. Suppose you have a single client process and it spends 1% of its time in LFS. Would you gain a lot by engaging in a tuning exercise that aimed exclusively at reducing this wait event? Well, obviously the upper limit on the gain you might make is 1% even if you eliminated these events in their entirety; the answer is, therefore, NO. You are unlikely to waste your time though because your Statspack or AWR report will probably not highlight the event in the first place.</p>
<p>On the other hand, suppose you have 10,000 sessions connecting to your database and 300 of these are active at any one time. Suppose that 1% of the active time of each session is spent in LFS would you gain much by tuning the event? The answer is the same as before: NO. However, you might be misled by Statspack or AWR because now the total wait time for the database for LFS will be 3 hours in any one hour! This is because at any one time we have 300 active sessions and on average three of them will be in LFS. That comes out to be 3 hours per hour waiting! So before you conclude that LFS is a major performance issue first check out what the <em>average</em> wait time per active session is. Of course, what you should really do is find out the percentage of wait time for your <em>business critical processes</em> but I digress again. Let us get back to the technicalities of LFS.</p>
<p><strong>‘Log File Sync’ (LFS) versus ‘log file parallel write’ (LFPW)</strong></p>
<p>When a transaction commits it will usually wait for its redo data to be recorded on disk before returning control to the application. That wait is LFS. There are three main exceptions to this:</p>
<ul>
<li>When the commit is issued within a PL/SQL block. In this case the wait will be deferred until the block returns. If the PL/SQL block contains multiple commit statements then only one wait will occur when the block returns.</li>
<li>When the commit statement is actually COMMIT WORK NOWAIT. In this case, there is no LFS wait. See part 3 in this series for an explanation of why this is dangerous.</li>
<li> When the session parameter COMMIT_WAIT (COMMIT_WRITE in earlier releases) is set to NOWAIT. This parameter can be set at the instance level but this is dangerous practice and should be set at the session level only with care. See part 3 in this series for an explanation of why this is dangerous.</li>
</ul>
<p>The waiting client process does not do the I/O itself. LGWR does. When LGWR does the I/O it waits on <strong>‘log file parallel write’ (LFPW).</strong> So does this mean that the number of LFPW events should equal the number of LFS events? NO! This is because LGWR employs a tactic known as <strong>‘group write’</strong> and may write out the redo data for multiple transactions and multiple sessions in one go. Suppose you have 5 client processes each of which is waiting for its transaction to complete. These transactions will be waiting for LFS. LGWR will be waiting for LFPW. If the I/O takes 10ms then:</p>
<ul>
<li>The system will accumulate 50ms of time in LFS (10ms for each client process). 5 wait events will be recorded.</li>
<li>LGWR will accumulate 10ms. One wait event will be recorded.</li>
</ul>
<p>That is 6 wait events totalling 60ms clocked up for one 10ms I/O! You see the danger in oversimplifying?</p>
<p>In the simplified example above I have suggested that the average wait time for LFS should be about the same as the average wait time for LFPW as they are both associated with waiting for the redo data to be written to disk. In fact, this is itself a misleading simplification. Onto <a href="http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-2/">part 2</a>.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/425/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/425/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/425/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=425&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/07/24/log-file-sync-and-log-file-parallel-write-part-1/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>MAXIMUM/MINIMUM Column Statistics – Part 2</title>
		<link>http://tonyhasler.wordpress.com/2011/05/07/maximumminimum-column-statistics-%e2%80%93-part-2/</link>
		<comments>http://tonyhasler.wordpress.com/2011/05/07/maximumminimum-column-statistics-%e2%80%93-part-2/#comments</comments>
		<pubDate>Sat, 07 May 2011 19:33:20 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=406</guid>
		<description><![CDATA[Well it is now over four months since I wrote the part 1 of this blog. Apologies for the delay but I have been working long hours and travelling so my blog never made it to the top of the pile. Let us now address what happens when we try to use NULL for maximum/minimum [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=406&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Well it is now over four months since I wrote the <a href="http://tonyhasler.wordpress.com/2011/01/23/maximumminimum-column-statistics-part-1/">part 1 of this blog</a>. Apologies for the delay but I have been working long hours and travelling so my blog never made it to the top of the pile.</p>
<p>Let us now address what happens when we try to use NULL for maximum/minimum values by modifying the script in part 1:</p>
<pre><code>DROP TABLE t1;
CREATE TABLE t1 (a NUMBER, b DATE, c TIMESTAMP, d TIMESTAMP WITH LOCAL TIME ZONE,
 e RAW(32), f VARCHAR2(32),g NVARCHAR2(32),h CHAR(32),
i INTERVAL DAY TO SECOND,j INTERVAL YEAR TO MONTH, k ROWID,
l TIMESTAMP WITH TIME ZONE,m NCHAR(32));

DECLARE
   srec   DBMS_STATS.statrec;
BEGIN
   DBMS_STATS.set_table_stats (ownname            =&gt; USER,
                               tabname            =&gt; 'T1',
                               numrows            =&gt; 20000,
                               numblks            =&gt; 1000,
                               avgrlen            =&gt; 400,
                               no_invalidate      =&gt; FALSE);

   FOR r IN (SELECT *
               FROM user_tab_cols
              WHERE table_name = 'T1')
   LOOP
      srec.epc := 2;                       -- Two endpoints
      srec.bkvals := NULL;                  -- No histogram
      DBMS_STATS.delete_column_stats
                                  (ownname      =&gt; USER,
                                   tabname      =&gt; 'T1',
                                   colname      =&gt; r.column_name);
      DBMS_STATS.prepare_column_values
                                 (srec,
                                  DBMS_STATS.rawarray (NULL,

                                                       -- Minimum
                                                       NULL
                                                           -- Maximum
                                  ));
      -- srec.novals := dbms_stats.numarray(-9.9e125,9.9e125) ; -- Special fix
      DBMS_STATS.set_column_stats (ownname       =&gt; USER,
                                   tabname       =&gt; 'T1',
                                   partname      =&gt; NULL,
                                   colname       =&gt; r.column_name,
                                   distcnt       =&gt; NULL,
                                   density       =&gt; NULL,
                                   nullcnt       =&gt; 0,
                                   srec          =&gt; srec,
                                   avgclen       =&gt; 100);
   END LOOP;
END;
/

SET autotrace off
COLUMN lowval format a10
COLUMN hival format a10
SET null '(null)'

SELECT distcnt, density, h.lowval, h.hival, h.MINIMUM,
       h.maximum
  FROM SYS.obj$ o, SYS.hist_head$ h
 WHERE o.NAME = 'T1'
   AND o.owner# = USERENV ('SCHEMAID')
   AND o.obj# = h.obj#;

SET autotrace traceonly explain
SELECT *
  FROM t1
 WHERE a &gt; -1;
SELECT *
  FROM t1
 WHERE a &gt; 0;
SELECT *
  FROM t1
 WHERE a = 0;
SELECT *
  FROM t1
 WHERE a  0;
SELECT *
  FROM t1
 WHERE a = 1;
SELECT *
  FROM t1
 WHERE a  1;</code></pre>
<p>The above script generates the following output:</p>
<pre><code>   DISTCNT    DENSITY LOWVAL     HIVAL         MINIMUM    MAXIMUM
---------- ---------- ---------- ---------- ---------- ----------
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0
         0          0 (null)     (null)              0          0

13 rows selected.

SQL&gt;
SQL&gt; SET autotrace traceonly explain
SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a &gt; -1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;(-1))

SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a &gt; 0;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;0)

SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a = 0;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"=0)

SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a  0;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"0)

SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a = 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"=1)

SQL&gt; SELECT *
  2    FROM t1
  3   WHERE a  1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 19999 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 19999 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"1)

SQL&gt; select * from t1 where a  3000;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 19999 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 19999 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"3000)

SQL&gt; select * from t1 where a  -1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 19999 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 19999 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"(-1))

SQL&gt; select * from t1 where a  0;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"0)

SQL&gt; select * from t1 where a &lt; 1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;A&quot; select * from t1 where a &lt; 0;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;A&quot; select * from t1 where a &lt; -1;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;A&quot; select * from t1 where a &lt; 2;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;A&quot; select * from t1 where a &lt;= 2;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter(&quot;A&quot;&lt;=2)</code></pre>
<p>The first thing to note is that zeroes have been added for the DISTCNT, DENSITY, MINIMUM and MAXIMUM columns in HIST_HEAD$. This causes the selectivity estimate of queries involving a range predicate to be 100% if the range includes 0 and 0% (cardinality 1) when the range does not include 0. Equality and inequality predicates yield 0% and 100% respectively regardless of whether zero is specified as the value (although not the slight discrepancy in the inequality value when zero is not specified).</p>
<p>If you change the script to specify non-zero values for the density and a distcnt greater than 1 you will find that the behaviour changes as follows:</p>
<pre><code>&gt; -1 : 100%</code>
<code>&gt; 0 : (1-density)*100%</code>
<code>= 0 : density*100%</code>
<code>&lt;&gt; 0 : (1-density)*100%</code>
<code>= 1 : density*100%</code>
<code>&lt;&gt; 1 : (1-density)*100%</code></pre>
<p>The bottom line is that this mechanism is probably too complex to be of practical use and the technique of using very diffent values documented in <a href="http://tonyhasler.wordpress.com/2011/01/23/maximumminimum-column-statistics-part-1/">part 1 of this blog</a> will work much better.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/406/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/406/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/406/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=406&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/05/07/maximumminimum-column-statistics-%e2%80%93-part-2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>MAXIMUM/MINIMUM Column Statistics &#8211; Part 1</title>
		<link>http://tonyhasler.wordpress.com/2011/01/23/maximumminimum-column-statistics-part-1/</link>
		<comments>http://tonyhasler.wordpress.com/2011/01/23/maximumminimum-column-statistics-part-1/#comments</comments>
		<pubDate>Sun, 23 Jan 2011 21:32:37 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=389</guid>
		<description><![CDATA[I presented a talk at the 2010 UKOUG national conference entitled &#8220;Stabilising Statistics&#8221; (materials available here) that suggested setting the minimum and maximum values for column statistics to very low and very high values respectively. This technique contributes to execution plan stability. I will refer to this technique as VDV (very different values) for the [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=389&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I presented a talk at the 2010 UKOUG national conference entitled &#8220;Stabilising Statistics&#8221; (materials available <a href="http://www.aberdour.demon.co.uk/stabilising_statistics.zip">here</a>) that suggested setting the minimum and maximum values for column statistics to very low and very high values respectively. This technique contributes to execution plan stability. I will refer to this technique as VDV (very different values) for the rest of this note.</p>
<p>During that presentation Wolfgang Breitling posed the question &#8220;What happens if you set the maximum and minimum values to NULL&#8221;?</p>
<p>I couldn&#8217;t answer him at the time and I promised that I would post an answer on my blog.</p>
<p>You will have to wait for the answer a little bit more I am afraid because I must first post some principles that I discovered whilst researching the answer.</p>
<p>If we dive down into the data dictionary we can find a a table called HIST_HEAD$. This table contains the minimum and maximum values for column statistics. What is interesting is that there are two sets of minimum and maximum values. These pairs of values are MINIMUM/MAXIMUM (type NUMBER) and LOWVAL/HIVAL (type RAW(32)). As far as I can see, only datatypes CHAR,NCHAR, VARCHAR2, and NVARCHAR2 use the LOWVAL/HIVAL pair in cardinality calculations. All other datatypes use the MINIMUM/MAXIMUM pair (including RAW!).</p>
<p>Unfortunately, when you look at views such as USER_TAB_COLS all you see is the LOWVAL/HIVAL pair from sys.HIST_HEAD$ reported as LOW_VALUE/HIGH_VALUE. The other pair is completely hidden.</p>
<p>&#8220;So what&#8221;? You may ask. These values are equivalent aren&#8217;t they?</p>
<p>Well some of the time, yes. But not always.</p>
<p>Here is how you might set MIN/MAX column statistics yourself:</p>
<pre><code>DROP TABLE t1;
CREATE TABLE t1 (a NUMBER, b DATE, c TIMESTAMP, d TIMESTAMP WITH LOCAL TIME ZONE,
 e RAW(32), f VARCHAR2(32),g NVARCHAR2(32),h CHAR(32),
i INTERVAL DAY TO SECOND,j INTERVAL YEAR TO MONTH, k ROWID,
l TIMESTAMP WITH TIME ZONE,m nchar(32));

DECLARE
   srec   DBMS_STATS.statrec;
BEGIN
   DBMS_STATS.set_table_stats (ownname            =&gt; USER,
                               tabname            =&gt; 'T1',
                               numrows            =&gt; 20000,
                               numblks            =&gt; 1000,
                               avgrlen            =&gt; 400,
                               no_invalidate      =&gt; FALSE);

   FOR r IN (SELECT *
               FROM user_tab_cols
              WHERE table_name = 'T1')
   LOOP
      srec.epc := 2;                       -- Two endpoints
      srec.bkvals := NULL;                  -- No histogram
      DBMS_STATS.prepare_column_values
                                (srec,
                                 DBMS_STATS.rawarray (
                                 hextoraw('000000000000000000000000000000'),
                                                      -- Minimum
                                 hextoraw('ffffffffffffffffffffffffffffff')
                                                       -- Maximum
                                 ));
      -- srec.novals := dbms_stats.numarray(-9.9e125,9.9e125) ; -- Special fix
      DBMS_STATS.set_column_stats
                                 (ownname       =&gt; USER,
                                  tabname       =&gt; 'T1',
                                  partname      =&gt; NULL,
                                  colname       =&gt; r.column_name,
                                  distcnt       =&gt; 2,
                                  density       =&gt; 0.5,
                                  nullcnt       =&gt; 0,
                                  srec          =&gt; srec,
                                  avgclen       =&gt; 100);
   END LOOP;
END;
/
set autotrace off
column lowval format a30
column hival format a30
select h.lowval,h.hival,h.minimum,h.maximum
from sys.obj$ o,sys.hist_head$ h
where o.name='T1'
  and o.owner# = userenv('SCHEMAID')
  and o.obj#=h.obj# ;
--
set autotrace traceonly explain
select * from t1 where a &gt; 1e30 ;
select * from t1 where a &gt; 1e37 ;
select * from t1 where a &lt; 1e30 ;
select * from t1 where a &lt; 1e37 ;
select * from t1 where a &gt;= 1e30 ;
select * from t1 where a &gt;= 1e37 ;
select * from t1 where a &lt;= 1e30 ;
select * from t1 where a &lt;= 1e37 ;
</code></pre>
<p>This code sets the columns statistics to VDV and examines the consequences.<br />
This is what we get:</p>
<pre><code>LOWVAL                         HIVAL                             MINIMUM    MAXIMUM
------------------------------ ------------------------------ ---------- ----------
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF          0 1.3292E+36
13 rows selected.

SQL&gt; --
SQL&gt; set autotrace traceonly explain
SQL&gt; select * from t1 where a &gt; 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;1e30)

SQL&gt; select * from t1 where a &gt; 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;1e37)

SQL&gt; select * from t1 where a &lt; 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;1e30) SQL&gt; select * from t1 where a &lt; 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;1e37) SQL&gt; select * from t1 where a &gt;= 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;=1e30)

SQL&gt; select * from t1 where a &gt;= 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |   400 |   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   |     1 |   400 |   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;=1e37)

SQL&gt; select * from t1 where a &lt;= 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;=1e30) SQL&gt; select * from t1 where a &lt;= 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;=1e37)
 </code></pre>
<p>You can see that cardinality estimates are badly affected when very large values are supplied in the predicates. We can get much more intelligible - and consistent &#8211; results by setting NOVALS ourselves. This is what happens when you uncomment the line labeled &#8216;Special Fix&#8217; above.</p>
<pre><code>LOWVAL                         HIVAL                             MINIMUM    MAXIMUM
------------------------------ ------------------------------ ---------- ----------
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
000000000000000000000000000000 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -9.90E+125 9.900E+125
13 rows selected.

SQL&gt; --
SQL&gt; set autotrace traceonly explain
SQL&gt; select * from t1 where a &gt; 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;1e30)

SQL&gt; select * from t1 where a &gt; 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;1e37)

SQL&gt; select * from t1 where a &lt; 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;1e30) SQL&gt; select * from t1 where a &lt; 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 10000 |  3906K|   222   (1)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 10000 |  3906K|   222   (1)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;1e37) SQL&gt; select * from t1 where a &gt;= 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;=1e30)

SQL&gt; select * from t1 where a &gt;= 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&gt;=1e37)

SQL&gt; select * from t1 where a &lt;= 1e30 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;=1e30) SQL&gt; select * from t1 where a &lt;= 1e37 ;

Execution Plan
----------------------------------------------------------
Plan hash value: 3617692013

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      | 20000 |  7812K|   223   (2)| 00:00:03 |
|*  1 |  TABLE ACCESS FULL| T1   | 20000 |  7812K|   223   (2)| 00:00:03 |
--------------------------------------------------------------------------

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

   1 - filter("A"&lt;=1e37)
</code></pre>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/389/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/389/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/389/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=389&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/01/23/maximumminimum-column-statistics-part-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
		<item>
		<title>UKOUG national conference 2010</title>
		<link>http://tonyhasler.wordpress.com/2011/01/03/ukoug-national-conference-2010/</link>
		<comments>http://tonyhasler.wordpress.com/2011/01/03/ukoug-national-conference-2010/#comments</comments>
		<pubDate>Mon, 03 Jan 2011 17:16:18 +0000</pubDate>
		<dc:creator>tonyhasler</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tonyhasler.wordpress.com/?p=383</guid>
		<description><![CDATA[As I am sure the vast majority of readers of this blog will know, &#8220;Blog&#8221; stands for &#8220;Web Log&#8221; and reflects the fact that most blogs are diaries of one form or another.  Mine is one of a minority of blogs in the world (though probably not a minority fo Oracle blogs) that are used [...]<img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=383&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>As I am sure the vast majority of readers of this blog will know, &#8220;Blog&#8221; stands for &#8220;Web Log&#8221; and reflects the fact that most blogs are diaries of one form or another.  Mine is one of a minority of blogs in the world (though probably not a minority fo Oracle blogs) that are used as a way of publishing purely technical material, rather than personal experiences.</p>
<p>This entry is a bit different in that, although the material is largely technical,it does indeed reflect some personal experiences.  Apologies to those of you who find the change of tone disappointing; it is not a sign of things to come.</p>
<p>The first, and most important, thing I have to discuss about this years conference concerns a question <a href="http://www.centrexcc.com/">Wolfgang Breitling</a> asked me during my &#8220;Stabilising Statistics&#8221; talk.  This presentation (material available for download <a href="http://www.aberdour.demon.co.uk/stabilising_statistics.zip">here</a>) focussed on improving execution plan stability without the use of stored outlines or SQL baselines.  One of the items I discussed was how execution plans changed as a result of bind variable values moving beyond the minimum and maximum values recorded in column statistics.  This question was of considerable interest to Wolfgang as he had himself presented a paper in 2009 on the same subject entitled &#8220;Seeding Statistics&#8221;.</p>
<p>Wolfgang&#8217;s original technique was to set the number of distinct values to 2 and the minimum and maximum values the same!  Of course this cannot happen in real life: if the minimum and maximum are the same then there is only one value.  Wolfgang reported that he got good, stable plans with this approach but my experiments suggested that setting the minimum value of the column to a very low value and the maximum to a very high value achieved better results.</p>
<p>Wolfgang&#8217;s question was this:  &#8220;What happens if you set the minimum and maximum values to NULL&#8221;?  Well, I knew that I had tried it and that the results were not satisfactory but I didn&#8217;t recall the specifics of my incomplete analysis.  I promised to blog an answer.  Well it turns out that the topic is quite complicated and Wolfgang and I have collaborated on some research over the holiday period and I will publish the results soon.</p>
<p>The second experience I wanted to share with you was an enlightening conversation I had with a few new friends in the pub.  The topic was Oracle security.  This important topic is something that I find rather dry and have not thought about as much as other aspects of Oracle database technology &#8211; such as performance.  I was talking to <a href="http://oraganism.wordpress.com/author/martinpaulnash/">Martin Nash</a>, <a href="http://oraganism.wordpress.com/author/nj1973/">Neil Johnson</a>, <a href="http://oraganism.wordpress.com/author/benjaminathompson/">Ben Thompson</a> and others.  I suggested that if your data was sensitive in nature you had to buy Database Vault as DBAs would otherwise be able to see your data.  It was reminded that privileges such as ALTER DATABASE, ALTER USER, and the SELECT_CATALOG role did not allow a DBA to read any business data at all.  In fact, as long as you control the rare occasions when a database needs to be bounced (that does require SYSDBA) your DBAs should be able to do most if not all of their work without having access to any business data!  Limiting DBA privileges may not be as complete a solution to role segregation as Database Vault but when I thought about it I realised that it certainly goes further than I had previously realised.</p>
<p>Last but not least I want to mention the other event that I was involved in at the conference:  a debate with <a href="http://jonathanlewis.wordpress.com/">Jonathan Lewis</a> entitled &#8220;Does Oracle Ignore Hints&#8221;?  One thing that I learned from Jonathan during the preparation for my debate was that the technical analysis in <a href="http://tonyhasler.wordpress.com/2009/06/08/cbo-behaviour-with-indexes-on-tables-partitioned-by-list/">this</a> entry was completely wrong.  I have posted a correction <a href="http://tonyhasler.wordpress.com/2009/06/08/cbo-behaviour-with-indexes-on-tables-partitioned-by-list/#comment-207#comment-207">here</a>.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/tonyhasler.wordpress.com/383/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/tonyhasler.wordpress.com/383/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/tonyhasler.wordpress.com/383/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=tonyhasler.wordpress.com&amp;blog=4263781&amp;post=383&amp;subd=tonyhasler&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://tonyhasler.wordpress.com/2011/01/03/ukoug-national-conference-2010/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://0.gravatar.com/avatar/238d8e3b0ec2784e7f30a442e8c999e8?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">tonyhasler</media:title>
		</media:content>
	</item>
	</channel>
</rss>
