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

<channel>
	<title>Striving for Optimal Performance &#187; Indexes</title>
	<atom:link href="http://antognini.ch/category/oracledatabase/indexes/feed/" rel="self" type="application/rss+xml" />
	<link>http://antognini.ch</link>
	<description></description>
	<lastBuildDate>Sat, 17 Dec 2011 23:42:45 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Challenges and Chances of the 11g Query Optimizer</title>
		<link>http://antognini.ch/2011/12/challenges-and-chances-of-the-11g-query-optimizer/</link>
		<comments>http://antognini.ch/2011/12/challenges-and-chances-of-the-11g-query-optimizer/#comments</comments>
		<pubDate>Mon, 12 Dec 2011 09:59:52 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Object Statistics]]></category>
		<category><![CDATA[Query Optimizer]]></category>
		<category><![CDATA[Speaking]]></category>
		<category><![CDATA[System Statistics]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=1665</guid>
		<description><![CDATA[Challenges and Chances of the 11g Query Optimizer is the name of a presentation I gave at several events (e.g. Trivadis Performance Days, Oracle OpenWorld, DOAG Konferenz, UKOUG Conference) throughout 2011. Its abstract is the following:
With every new release, the query optimizer is enhanced. Oracle Database 11g Release 1 and Release 2 are no exception [...]]]></description>
			<content:encoded><![CDATA[<p><em>Challenges and Chances of the 11g Query Optimizer</em> is the name of a presentation I gave at several events (e.g. Trivadis Performance Days, Oracle OpenWorld, DOAG Konferenz, UKOUG Conference) throughout 2011. Its abstract is the following:</p>
<blockquote><p>With every new release, the query optimizer is enhanced. Oracle Database 11g Release 1 and Release 2 are no exception to the rule. Specifically, they introduce key improvements in the following areas: indexing, optimization techniques, object statistics and plan stability. The aim of this presentation is to review the new features from a practical point of view as well as to point out challenges related to them. In other words, to let you know what you can expect from the query optimizer when you upgrade to Oracle Database 11g.</p></blockquote>
<p>The aim of this short post is to point out that I made available the current version of the slides and all the scripts that go with them <a href="/publications/">here</a>.</p>
<p>The structure of the presentation (incl. a reference to the available scripts) is the following:</p>
<ul>
<li>Observations
<ul>
<li>Number of Query Optimizer Parameters by Release</li>
<li>Number of Query Optimizer Bugs Fixed by Patchset</li>
</ul>
</li>
<li>Indexing
<ul>
<li>Invisible Indexes (ex_invisible_index.sql)</li>
<li>Index Support for Linguistic LIKE (ex_linguistic_like.sql)</li>
<li>INDEX REBUILD and Statistics History (ex_index_rebuild.sql)</li>
</ul>
</li>
<li>Optimization Techniques
<ul>
<li>Full Outer Join (ex_full_outer_join.sql)</li>
<li>Join-Filter Pruning (ex_join_filter_pruning.sql)</li>
<li>Table Expansion (ex_table_expansion.sql)</li>
<li>Join Factorization (ex_join_factorization.sql)</li>
<li>OR Expansion (ex_or_expansion.sql)</li>
<li>Join Elimination (ex_join_elimination.sql)</li>
<li>Subquery Unnesting (ex_subquery_unnesting.sql)</li>
</ul>
</li>
<li>System and Object Statistics (DBMS_STATS)
<ul>
<li>Workload System Statistics</li>
<li>Object Statistics – Default Preferences</li>
<li>Object Statistics – Auto Sample Size</li>
<li>Object Statistics – Pending Statistics (ex_pending_object_statistics.sql)</li>
<li>Object Statistics – Incremental Statistics (ex_incremental_stats.sql)</li>
<li>Object Statistics – Extended Statistics on Expressions (ex_extended_statistics1.sql)</li>
<li>Object Statistics – Extended Statistics on Column Groups (ex_extended_statistics2.sql)</li>
<li>Object Statistics – Seeding Column Groups</li>
<li>Object Statistics – Comparing Statistics (ex_comparing_statistics.sql)</li>
<li>Object Statistics – Locks not Exported</li>
<li>JOB_QUEUE_PROCESSES</li>
</ul>
</li>
<li>Plan Stability
<ul>
<li>CURSOR_SHARING</li>
<li>SQL Plan Baselines  (ex_execution_plan_stability.sql, ex_execution_plan_stability_10g.sql, ex_execution_plan_stability_11g.sql)</li>
<li>Stored Outlines</li>
<li>Adaptive Cursor Sharing (ex_bind_peeking.sql, ex_bind_peeking_bind_aware.sql)</li>
<li>Cardinality Feedback (ex_cardinality_feedback.sql)</li>
</ul>
</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2011/12/challenges-and-chances-of-the-11g-query-optimizer/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IS NULL Conditions and B-tree Indexes</title>
		<link>http://antognini.ch/2011/02/is-null-conditions-and-b-tree-indexes/</link>
		<comments>http://antognini.ch/2011/02/is-null-conditions-and-b-tree-indexes/#comments</comments>
		<pubDate>Thu, 17 Feb 2011 10:01:56 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Query Optimizer]]></category>
		<category><![CDATA[TOP]]></category>

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

Plan hash value: 780655320

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

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

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

Plan hash value: 780655320

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

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

Plan hash value: 780655320

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

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

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

Plan hash value: 3029444779

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

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

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

		<guid isPermaLink="false">http://antognini.ch/?p=1214</guid>
		<description><![CDATA[Recenty the following question was posted on oracle-l (I paraphrase&#8230;): 
With Oracle Database it is possible to create something similar to Teradata&#8217;s sparse indexes?
Since the question is an interesting one, I decided to write this short post.
First of all, I have to say that such a feature is not supported by the CREATE INDEX statement [...]]]></description>
			<content:encoded><![CDATA[<p>Recenty the following question was posted on <a href="http://www.freelists.org/post/oracle-l/oracle-equivalent-of-teradatas-sparse-index">oracle-l</a> (I paraphrase&#8230;): </p>
<blockquote><p>With Oracle Database it is possible to create something similar to Teradata&#8217;s sparse indexes?</p></blockquote>
<p>Since the question is an interesting one, I decided to write this short post.</p>
<p>First of all, I have to say that such a feature is not supported by the CREATE INDEX statement with Oracle Database. What a pity! I would really like to see this feature in Oracle Database 12x (I bet that &#8220;x&#8221; will be &#8220;f&#8221;; post you opinion as a comment if you want). Several database engines do so. Teradata was mentioned by the OP, two additional examples are <a href="http://www.postgresql.org/docs/current/static/sql-createindex.html">PostgreSQL</a> and <a href="http://msdn.microsoft.com/en-us/library/ms188783.aspx">SQL Server</a>.</p>
<p>How to do something similar with Oracle Database?</p>
<p>As of Oracle Database 11g Release 2 it is possible to partially index a table by taking advantage of <a href="/2009/11/zero-size-unusable-indexes-and-the-query-optimizer/">Zero-Size Unusable Indexes</a>. The following SQL statements show an example where only the data of August 2010 is indexed.</p>
<p><pre>SQL&gt; CREATE TABLE t AS
  2  SELECT rownum AS id, sysdate-mod(rownum,100) AS tim, rpad(&#039;*&#039;,50,&#039;*&#039;) AS pad
  3  FROM dual
  4  CONNECT BY level &lt;= 1000;

SQL&gt; CREATE INDEX i ON t (tim)
  2  GLOBAL PARTITION BY RANGE (tim) (
  3    PARTITION i_201001 VALUES LESS THAN (to_date(&#039;2010-02-01&#039;,&#039;YYYY-MM-DD&#039;)),
  4    PARTITION i_201002 VALUES LESS THAN (to_date(&#039;2010-03-01&#039;,&#039;YYYY-MM-DD&#039;)),
  5    PARTITION i_201003 VALUES LESS THAN (to_date(&#039;2010-04-01&#039;,&#039;YYYY-MM-DD&#039;)),
  6    PARTITION i_201004 VALUES LESS THAN (to_date(&#039;2010-05-01&#039;,&#039;YYYY-MM-DD&#039;)),
  7    PARTITION i_201005 VALUES LESS THAN (to_date(&#039;2010-06-01&#039;,&#039;YYYY-MM-DD&#039;)),
  8    PARTITION i_201006 VALUES LESS THAN (to_date(&#039;2010-07-01&#039;,&#039;YYYY-MM-DD&#039;)),
  9    PARTITION i_201007 VALUES LESS THAN (to_date(&#039;2010-08-01&#039;,&#039;YYYY-MM-DD&#039;)),
 10    PARTITION i_201008 VALUES LESS THAN (to_date(&#039;2010-09-01&#039;,&#039;YYYY-MM-DD&#039;)),
 11    PARTITION i_201009 VALUES LESS THAN (to_date(&#039;2010-10-01&#039;,&#039;YYYY-MM-DD&#039;)),
 12    PARTITION i_201010 VALUES LESS THAN (to_date(&#039;2010-11-01&#039;,&#039;YYYY-MM-DD&#039;)),
 13    PARTITION i_201011 VALUES LESS THAN (to_date(&#039;2010-12-01&#039;,&#039;YYYY-MM-DD&#039;)),
 14    PARTITION i_201012 VALUES LESS THAN (to_date(&#039;2011-01-01&#039;,&#039;YYYY-MM-DD&#039;)),
 15    PARTITION i_maxvalue VALUES LESS THAN (MAXVALUE)
 16  )
 17  UNUSABLE;

SQL&gt; ALTER INDEX i REBUILD PARTITION i_201008;</pre></p>
<p>It goes without saying that you are not forced to have so many partitions in place. In fact, to index the data of August, the following CREATE INDEX is more appropriate.</p>
<p><pre>SQL&gt; CREATE INDEX i ON t (tim)
  2  GLOBAL PARTITION BY RANGE (tim) (
  3    PARTITION i_201007 VALUES LESS THAN (to_date(&#039;2010-08-01&#039;,&#039;YYYY-MM-DD&#039;)),
  4    PARTITION i_201008 VALUES LESS THAN (to_date(&#039;2010-09-01&#039;,&#039;YYYY-MM-DD&#039;)),
  5    PARTITION i_maxvalue VALUES LESS THAN (MAXVALUE)
  6  )
  7  UNUSABLE;</pre></p>
<p>Then, to index the data of September, you have to execute some SQL statements like the following ones.</p>
<ul>
<li>Create a partition for the data of September and rebuild it:</li>
</ul>
<p><pre>SQL&gt; ALTER INDEX i SPLIT PARTITION i_maxvalue AT (to_date(&#039;2010-10-01&#039;,&#039;YYYY-MM-DD&#039;)) INTO (
  2    PARTITION i_201009,
  3    PARTITION i_maxvalue
  4  );

SQL&gt; ALTER INDEX i REBUILD PARTITION i_201009;</pre></p>
<ul>
<li>Drop the oldest partition:</li>
</ul>
<p><pre>SQL&gt; ALTER INDEX i DROP PARTITION i_201007;</pre></p>
<ul>
<li>Make unusable the partition for the data of August:</li>
</ul>
<p><pre>SQL&gt; ALTER INDEX i MODIFY PARTITION i_201008 UNUSABLE;</pre></p>
<p>In this way you have at most three partitions available. And, for most of the time, only one of them is usable and, therefore, occupying space.</p>
<p>Even though in the example I provide in this post I use a global index, you can use the same technique with local indexes as well. That said, I see no problem in using a global index as the one shown in this post.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2010/08/partially-index-a-table/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>Does CREATE INDEX Gather Global Statistics?</title>
		<link>http://antognini.ch/2009/12/does-create-index-gather-global-statistics/</link>
		<comments>http://antognini.ch/2009/12/does-create-index-gather-global-statistics/#comments</comments>
		<pubDate>Thu, 17 Dec 2009 08:54:04 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[10gR1]]></category>
		<category><![CDATA[10gR2]]></category>
		<category><![CDATA[11gR1]]></category>
		<category><![CDATA[11gR2]]></category>
		<category><![CDATA[9iR2]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Object Statistics]]></category>
		<category><![CDATA[Partitioning]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=792</guid>
		<description><![CDATA[You can add the COMPUTE STATISTICS clause to the CREATE INDEX statement. It instructs the SQL statement to gather and store index statistics in the data dictionary, while creating the index. This is useful because the overhead associated with the gathering of statistics while executing this SQL statement is negligible. In Oracle9i, the gathering of [...]]]></description>
			<content:encoded><![CDATA[<p>You can add the COMPUTE STATISTICS clause to the CREATE INDEX statement. It instructs the SQL statement to gather and store index statistics in the data dictionary, while creating the index. This is useful because the overhead associated with the gathering of statistics while executing this SQL statement is negligible. In Oracle9i, the gathering of statistics is performed only when this clause is specified. As of Oracle Database 10g, whenever statistics are not locked, their gathering is done by default, which means the COMPUTE STATISTICS clause is deprecated and available for backward compatibility only.</p>
<p>Unfortunately, CREATE INDEX does not gather global statistics. As a result, whenever you are creating partitioned indexes, the global statistics might be inaccurate. Let me show you an example:</p>
<ul>
<li>Create partitioned table, insert data (notice that the number of distinct values is equal to the number of rows) and create a local index</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t (n1 number, n2 number)
  2  PARTITION BY RANGE (n1) (
  3    PARTITION p1 VALUES LESS THAN (11),
  4    PARTITION p2 VALUES LESS THAN (21)
  5  );

Table created.

SQL&gt; INSERT INTO t
  2  SELECT rownum, rownum
  3  FROM dual
  4  CONNECT BY level &lt;= 20;

20 rows created.

SQL&gt; CREATE INDEX i ON t (n2) LOCAL;

Index created.</pre></p>
<ul>
<li>The CREATE INDEX statement gathered the statistics for the index; let&#8217;s check them&#8230;</li>
</ul>
<p><pre>SQL&gt; SELECT partition_name, global_stats, distinct_keys
  2  FROM user_ind_statistics
  3  WHERE index_name = &#039;I&#039;;

PARTITION_NAME GLOBAL_STATS DISTINCT_KEYS
-------------- ------------ -------------
               NO                      10
P1             NO                      10
P2             NO                      10</pre></p>
<p>As you can see 1) the number of distinct keys at the global level is wrong; it should be 20! 2) the GLOBAL_STATS column at the index level is set to NO. As a result, when you create a partitioned index, you should manually gather the global index statistics straight after. In other words, you should do the following:</p>
<ul>
<li>Manually gather global level index statistics</li>
</ul>
<p><pre>SQL&gt; execute dbms_stats.gather_index_stats(ownname=&gt;user, indname=&gt;&#039;i&#039;, granularity=&gt;&#039;global&#039;)

PL/SQL procedure successfully completed.</pre></p>
<ul>
<li>Check whether the index statistics are accurate</li>
</ul>
<p><pre>SQL&gt; SELECT partition_name, global_stats, distinct_keys
  2  FROM user_ind_statistics
  3  WHERE index_name = &#039;I&#039;;

PARTITION_NAME GLOBAL_STATS DISTINCT_KEYS
-------------- ------------ -------------
               YES                     20
P1             NO                      10
P2             NO                      10</pre></p>
<p>There are situations, however, where it is not necessary to manually gather the global index statistics. For example, when the index is prefixed. But, as a general rule, I would not rely on the automatically gathered statistics for partitioned indexes.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2009/12/does-create-index-gather-global-statistics/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Zero-Size Unusable Indexes and the Query Optimizer</title>
		<link>http://antognini.ch/2009/11/zero-size-unusable-indexes-and-the-query-optimizer/</link>
		<comments>http://antognini.ch/2009/11/zero-size-unusable-indexes-and-the-query-optimizer/#comments</comments>
		<pubDate>Wed, 25 Nov 2009 19:25:03 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[11gR2]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Partitioning]]></category>
		<category><![CDATA[Query Optimizer]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=760</guid>
		<description><![CDATA[Zero-size unusable indexes and index partions is a small but useful feature of Oracle Database 11g Release 2. Simply put, its aim is to save space in the database by immediately releasing the segment associated to unusable indexes or index partitions. To illustrate this, let&#8217;s have a look to an example…

Create a partitioned table, insert [...]]]></description>
			<content:encoded><![CDATA[<p><em>Zero-size unusable indexes and index partions</em> is a small but useful feature of Oracle Database 11g Release 2. Simply put, its aim is to save space in the database by immediately releasing the segment associated to unusable indexes or index partitions. To illustrate this, let&#8217;s have a look to an example…</p>
<ul>
<li>Create a partitioned table, insert data, create a local index and gather object statistics:</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t (
  2    id NUMBER NOT NULL,
  3    d DATE NOT NULL,
  4    n NUMBER NOT NULL,
  5    pad VARCHAR2(4000) NOT NULL
  6  )
  7  PARTITION BY RANGE (d) (
  8    PARTITION t_jan_2009 VALUES LESS THAN (to_date(&#039;2009-02-01&#039;,&#039;yyyy-mm-dd&#039;)),
  9    PARTITION t_feb_2009 VALUES LESS THAN (to_date(&#039;2009-03-01&#039;,&#039;yyyy-mm-dd&#039;)),
 10    PARTITION t_mar_2009 VALUES LESS THAN (to_date(&#039;2009-04-01&#039;,&#039;yyyy-mm-dd&#039;)),
 11    PARTITION t_apr_2009 VALUES LESS THAN (to_date(&#039;2009-05-01&#039;,&#039;yyyy-mm-dd&#039;)),
 12    PARTITION t_may_2009 VALUES LESS THAN (to_date(&#039;2009-06-01&#039;,&#039;yyyy-mm-dd&#039;)),
 13    PARTITION t_jun_2009 VALUES LESS THAN (to_date(&#039;2009-07-01&#039;,&#039;yyyy-mm-dd&#039;)),
 14    PARTITION t_jul_2009 VALUES LESS THAN (to_date(&#039;2009-08-01&#039;,&#039;yyyy-mm-dd&#039;)),
 15    PARTITION t_aug_2009 VALUES LESS THAN (to_date(&#039;2009-09-01&#039;,&#039;yyyy-mm-dd&#039;)),
 16    PARTITION t_sep_2009 VALUES LESS THAN (to_date(&#039;2009-10-01&#039;,&#039;yyyy-mm-dd&#039;)),
 17    PARTITION t_oct_2009 VALUES LESS THAN (to_date(&#039;2009-11-01&#039;,&#039;yyyy-mm-dd&#039;)),
 18    PARTITION t_nov_2009 VALUES LESS THAN (to_date(&#039;2009-12-01&#039;,&#039;yyyy-mm-dd&#039;)),
 19    PARTITION t_dec_2009 VALUES LESS THAN (to_date(&#039;2010-01-01&#039;,&#039;yyyy-mm-dd&#039;))
 20  );

SQL&gt; INSERT INTO t
  2  SELECT rownum, to_date(&#039;2009-01-01&#039;,&#039;yyyy-mm-dd&#039;)+rownum/274, mod(rownum,11), rpad(&#039;*&#039;,100,&#039;*&#039;)
  3  FROM dual
  4  CONNECT BY level &lt;= 100000;

SQL&gt; CREATE INDEX i ON t (d) LOCAL;

SQL&gt; execute dbms_stats.gather_table_stats(user,&#039;T&#039;)</pre></p>
<ul>
<li>Make all partitions but the last one unusable:</li>
</ul>
<p><pre>SQL&gt; ALTER INDEX i MODIFY PARTITION t_jan_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_feb_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_mar_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_apr_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_may_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_jun_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_jul_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_aug_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_sep_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_oct_2009 UNUSABLE;

SQL&gt; ALTER INDEX i MODIFY PARTITION t_nov_2009 UNUSABLE;</pre></p>
<ul>
<li>Check whether the segments associated to the unusable partitions still exist:</li>
</ul>
<p><pre>SQL&gt; SELECT partition_name, bytes
  2  FROM user_segments
  3  WHERE segment_name = &#039;I&#039;
  4  AND segment_type = &#039;INDEX PARTITION&#039;
  5  ORDER BY partition_name;

PARTITION_NAME       BYTES
--------------- ----------
T_DEC_2009          262144</pre></p>
<p>As you can see from the output of the last query, only the segment associated to the partition T_DEC_2009 exists. All other segments have been freed. (Note that up to Oracle Database 11g Release 1 all segments would still exist.)</p>
<p>This is nice but, in my opinion, there is a more important thing to consider&#8230;<br />
What does the query optimizer do when it has to generate the execution plan for a query that reads data stored into a table having unusable index partitions? For example, does the query optimizer take advantage of the usable partitions to apply a restriction? If yes, what happens when both partitions having usable and unusable index partitions have to be accessed? Let&#8217;s have a look to some examples…</p>
<ul>
<li>First, let’s check whether the usable index partition can be used to apply a restriction:</li>
</ul>
<p><pre>SQL&gt; SELECT count(d)
  2  FROM t
  3  WHERE d BETWEEN to_date(&#039;2009-12-01 23:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;)
  4              AND to_date(&#039;2009-12-02 01:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;);

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

--------------------------------------------------------
| Id  | Operation               | Name | Pstart| Pstop |
--------------------------------------------------------
|   0 | SELECT STATEMENT        |      |       |       |
|   1 |  SORT AGGREGATE         |      |       |       |
|   2 |   PARTITION RANGE SINGLE|      |    12 |    12 |
|   3 |    INDEX RANGE SCAN     | I    |    12 |    12 |
--------------------------------------------------------</pre></p>
<p>Nice, an index range scan can be performed.</p>
<p><br/></p>
<ul>
<li>Second, let’s check what happen when an unusable index partition would be accessed:</li>
</ul>
<p><pre>SQL&gt; SELECT count(d)
  2  FROM t
  3  WHERE d BETWEEN to_date(&#039;2009-11-01 23:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;)
  4              AND to_date(&#039;2009-11-02 01:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;);

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

--------------------------------------------------------
| Id  | Operation               | Name | Pstart| Pstop |
--------------------------------------------------------
|   0 | SELECT STATEMENT        |      |       |       |
|   1 |  SORT AGGREGATE         |      |       |       |
|   2 |   PARTITION RANGE SINGLE|      |    11 |    11 |
|   3 |    TABLE ACCESS FULL    | T    |    11 |    11 |
--------------------------------------------------------</pre></p>
<p>Obviously, a partition scan is performed. Note that this is only true if the initialization parameter SKIP_UNUSABLE_INDEXES is set to TRUE (this is the default). Otherwise an ORA-01502 would be generated.</p>
<p><br/></p>
<ul>
<li>Third, let’s check what happens when both usable and unusable index partitions would be accessed:</li>
</ul>
<p><pre>SQL&gt; SELECT count(d)
  2  FROM t
  3  WHERE d BETWEEN to_date(&#039;2009-11-30 23:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;)
  4              AND to_date(&#039;2009-12-01 01:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;);

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

-------------------------------------------------------------
| Id  | Operation                 | Name    | Pstart| Pstop |
-------------------------------------------------------------
|   0 | SELECT STATEMENT          |         |       |       |
|   1 |  SORT AGGREGATE           |         |       |       |
|   2 |   VIEW                    | VW_TE_2 |       |       |
|   3 |    UNION-ALL              |         |       |       |
|   4 |     PARTITION RANGE SINGLE|         |    12 |    12 |
|   5 |      INDEX RANGE SCAN     | I       |    12 |    12 |
|   6 |     PARTITION RANGE SINGLE|         |    11 |    11 |
|   7 |      TABLE ACCESS FULL    | T       |    11 |    11 |
-------------------------------------------------------------</pre></p>
<p>Good stuff! The query optimizer generates an execution plan containing a UNION ALL to takes advantage of the usable index partition. This is really interesting because it allows us to selectively remove unnecessary index partitions. For example, in case some indexes are only used for the “current” partition(s), the index partitions of the older ones could be set unusable. As a result, lot of space might be released.</p>
<p>Be careful, however, that the flexibility of the query optimizer has (still?) some limits. For example, in the following query the unusable partition is the one in the “middle”. In such a case, I was not able to let the query optimizer apply the optimization described above.</p>
<p><pre>SQL&gt; ALTER INDEX i REBUILD PARTITION t_oct_2009;

SQL&gt; SELECT count(d)
  2  FROM t
  3  WHERE d BETWEEN to_date(&#039;2009-10-30 23:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;)
  4              AND to_date(&#039;2009-12-01 01:00:00&#039;,&#039;yyyy-mm-dd hh24:mi:ss&#039;);

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

----------------------------------------------------------
| Id  | Operation                 | Name | Pstart| Pstop |
----------------------------------------------------------
|   0 | SELECT STATEMENT          |      |       |       |
|   1 |  SORT AGGREGATE           |      |       |       |
|   2 |   PARTITION RANGE ITERATOR|      |    10 |    12 |
|   3 |    TABLE ACCESS FULL      | T    |    10 |    12 |
----------------------------------------------------------</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2009/11/zero-size-unusable-indexes-and-the-query-optimizer/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Invisible Indexes and Hints</title>
		<link>http://antognini.ch/2008/11/invisible-indexes-and-hints/</link>
		<comments>http://antognini.ch/2008/11/invisible-indexes-and-hints/#comments</comments>
		<pubDate>Tue, 11 Nov 2008 16:02:32 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[11gR1]]></category>
		<category><![CDATA[Bug]]></category>
		<category><![CDATA[Indexes]]></category>
		<category><![CDATA[Query Optimizer]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=56</guid>
		<description><![CDATA[In this post I would like to remove some misinformation about the utilization of hints with invisible indexes.
Let’s start by providing you what two sources say about that topic:

Oracle Database 11g: New Features for DBAs and Developers (by Sam R. Alapati and Charles Kim, page 132)
“If you want the optimizer to take the invisible index [...]]]></description>
			<content:encoded><![CDATA[<p>In this post I would like to remove some misinformation about the utilization of hints with invisible indexes.</p>
<p>Let’s start by providing you what two sources say about that topic:</p>
<ul>
<li><a href="http://apress.com/book/view/9781590599105">Oracle Database 11g: New Features for DBAs and Developers</a> (by Sam R. Alapati and Charles Kim, page 132)<br />
“If you want the optimizer to take the invisible index into account, you must use the index hint, as shown in the following example:“</li>
</ul>
<ul>
<li><a href="http://www.oracle.com/technology/pub/articles/oracle-database-11g-top-features/11g-schemamanagement.html">Oracle Database 11g: The Top New Features for DBAs and Developers &#8211; Schema Management</a> (by Arup Nanda)<br />
“To make the optimizer use the index again, you have to explicitly name the index in a hint:”</li>
</ul>
<p>Both sources point out that a hint can be used to compel the query optimizer to use an invisible index. Both also provide an example (not shown here). But, what is more important, both are wrong! In other words, a hint cannot be used to compel the query optimizer to use an invisible index.</p>
<p>What’s going on?!?</p>
<p>The problem is that this specific information used to be correct for the beta release of Oracle Database 11g. But that behavior was a bug, not a feature! Therefore, once the bug was fixed, this piece of information was no longer valid. </p>
<p>This is what happens when authors use beta releases for doing their tests&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2008/11/invisible-indexes-and-hints/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Invisible Indexes and Locks</title>
		<link>http://antognini.ch/2008/10/invisible-indexes-and-locks/</link>
		<comments>http://antognini.ch/2008/10/invisible-indexes-and-locks/#comments</comments>
		<pubDate>Fri, 17 Oct 2008 08:33:23 +0000</pubDate>
		<dc:creator>Christian Antognini</dc:creator>
				<category><![CDATA[11gR1]]></category>
		<category><![CDATA[Indexes]]></category>

		<guid isPermaLink="false">http://antognini.ch/?p=54</guid>
		<description><![CDATA[Invisible indexes are useful to temporarily hide an index from the query optimizer. In this post, instead of explaining what invisible indexes are, I would like to show whether the database engine uses invisible indexes to avoid false contention caused by unindexed foreign key.

Let&#8217;s start by creating two test tables with a relation (foreign key) [...]]]></description>
			<content:encoded><![CDATA[<p>Invisible indexes are useful to temporarily hide an index from the query optimizer. In this post, instead of explaining what invisible indexes are, I would like to show whether the database engine uses invisible indexes to avoid false contention caused by unindexed foreign key.</p>
<ul>
<li>Let&#8217;s start by creating two test tables with a relation (foreign key) between them:</li>
</ul>
<p><pre>SQL&gt; CREATE TABLE t1 AS SELECT rownum AS n FROM dual CONNECT BY level &lt;= 10;

Table created.

SQL&gt; CREATE TABLE t2 AS SELECT rownum AS n FROM dual CONNECT BY level &lt;= 10;

Table created.

SQL&gt; ALTER TABLE t1 ADD CONSTRAINT t1_uk UNIQUE (n);

Table altered.

SQL&gt; ALTER TABLE t2 ADD CONSTRAINT t1_t2_fk FOREIGN KEY (n) REFERENCES t1 (n);

Table altered.</pre></p>
<ul>
<li>Note that the foreign key is unindexed. Therefore, as shown by the following example, false contention due to locks is possible:</li>
</ul>
<table>
<tr>
<th>Session 1</th>
<th>Session 2</th>
</tr>
<tr>
<td><pre>SQL&gt; SELECT sys_context(&#039;userenv&#039;,&#039;sid&#039;) AS sid
  2  FROM dual;

SID
----
86

SQL&gt; INSERT INTO t2 VALUES (1);

1 row created.</pre></td>
<td></td>
</tr>
<tr>
<td></td>
<td><pre>SQL&gt; SELECT sys_context(&#039;userenv&#039;,&#039;sid&#039;) AS sid
  2  FROM dual;

SID
----
85

SQL&gt; DELETE FROM t2 WHERE n = 2;

1 row deleted.

SQL&gt; DELETE FROM t1 WHERE n = 2;</pre></td>
</tr>
<tr>
<td><pre>SQL&gt; SELECT blocking_session
  2  FROM v$session
  3  WHERE sid = 85;

BLOCKING_SESSION
----------------
              86

SQL&gt; ROLLBACK;

Rollback complete.</pre></td>
<td></td>
</tr>
<tr>
<td></td>
<td><pre>1 row deleted.

SQL&gt; ROLLBACK;

Rollback complete.</pre></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</table>
<ul>
<li>Now, let&#8217;s execute the same SQL statements when the the foreign key is indexed by an invisible index:</li>
</ul>
<table>
<tr>
<th>Session 1</th>
<th>Session 2</th>
</tr>
<tr>
<td><pre>SQL&gt; CREATE INDEX i ON t2 (n) INVISIBLE;

Index created.

SQL&gt; INSERT INTO t2 VALUES (1);

1 row created.</pre></td>
<td></td>
</tr>
<tr>
<td></td>
<td><pre>SQL&gt; DELETE FROM t2 WHERE n = 2;

1 row deleted.

SQL&gt; DELETE FROM t1 WHERE n = 2;

1 row deleted.</pre></td>
</tr>
</table>
<ul>
<li>As you can see there is no false contention due to locks.</li>
</ul>
<p>Conclusion: invisible indexes can only be used to assess how the query optimizer would behave when a specific index is (not) available. But, be careful, with them you cannot assess how an application would behave when a specific index is (not) available. In fact, except for access paths, invisible indexes are regularly updated and used for setting locks when DML statements are executed.</p>
]]></content:encoded>
			<wfw:commentRss>http://antognini.ch/2008/10/invisible-indexes-and-locks/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
	</channel>
</rss>

