<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" version="2.0"><channel><atom:id>tag:blogger.com,1999:blog-15440695</atom:id><lastBuildDate>Sun, 08 Nov 2009 19:38:27 +0000</lastBuildDate><title>THE Q U A D R O BLOG</title><description /><link>http://afatkulin.blogspot.com/</link><managingEditor>noreply@blogger.com (Alex Fatkulin)</managingEditor><generator>Blogger</generator><openSearch:totalResults>126</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/TheQUADROBlog" type="application/rss+xml" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com" /><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-2584198534657294</guid><pubDate>Thu, 06 Aug 2009 23:20:00 +0000</pubDate><atom:updated>2009-08-06T21:47:41.308-04:00</atom:updated><title>How to install Oracle Grid Control Agents on a Windows failover cluster with no downtime</title><description>Metalink Note:464191.1 describes steps required to configure Oracle Grid Control agent in Windows failover cluster environment. Unfortunately, as part of the configuration, the cluster disk containing virtual agent's state information have to be moved to the node where the agent is being deployed.&lt;br /&gt;&lt;br /&gt;The agent state directory has to "follow" virtual agent when a failover occurs, hence the requirement for it to be on a cluster disk resource. And since the cluster disk resource is visible on the active node only, you can not deploy virtual agent on any of the passive nodes without moving the group containing disk with state information first.&lt;br /&gt;&lt;br /&gt;The above is not a big deal when you're doing install on a brand new or development system but what if you have to deal with a production cluster where any potential downtime that might be associated with moving the group across the nodes would better be avoided?&lt;br /&gt;&lt;br /&gt;Let's say you have an Oracle FailSafe configuration and you intend to use Oracle Grid Control to monitor your Oracle database. In this case your virtual agent will be a part of the same cluster group where your Oracle database is. Failing over your database across all the nodes for the sake of deploying a virtual agent may not necessarily be what you want to do.&lt;br /&gt;&lt;br /&gt;Of course, the easy workaround is to add another disk (LUN) to a cluster, use it to deploy the agents and, once the deployment has been done, add it to the same group where your database is. But what if you do not have any spare disks and have to share the same cluster disk with your Oracle database?&lt;br /&gt;&lt;br /&gt;I gave this problem a bit of research and, as it turned out, there is a really simple workaround which may come in handy in case you'll be faced with the same problem.&lt;br /&gt;&lt;br /&gt;I'll use the following configuration as an example:&lt;br /&gt;&lt;pre&gt;ORA01A -- first (active) node.&lt;br /&gt;ORA01B -- second (passive) node.&lt;br /&gt;ORA01V -- Oracle Database VIP.&lt;br /&gt;c:\oracle\product\10.2.0\agent10g -- Oracle Grid Control agent home.&lt;/pre&gt;&lt;br /&gt;Let's say that each system has a local drive C: and the deployment will be done on a cluster drive D:.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Deploy virtual agent on the active node&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;This is where you follow exactly what Metalink note says you to do:&lt;br /&gt;&lt;pre&gt;C:\&gt;emctl deploy agent -n OracleAgentORA01V d:\agent10g ORA01V:1830 ORA01A:1830&lt;br /&gt;Creating shared install...&lt;br /&gt;Source location: C:\oracle\product\10.2.0\agent10g&lt;br /&gt;Destination (shared install) : d:\agent10g&lt;br /&gt;DeployMode : agent&lt;br /&gt;&lt;br /&gt;Creating directories...&lt;br /&gt;Creating targets.xml...&lt;br /&gt;Creating emctl control program...&lt;br /&gt;Creating emtgtctl control program...&lt;br /&gt;Setting log and trace files locations for Agent ...&lt;br /&gt;Secure agent found. New agent should be configured for secure mode&lt;br /&gt;&lt;br /&gt;Source Agent operating in secure mode.&lt;br /&gt;Run "d:\agent10g/bin/emctl secure agent" to secure agent&lt;br /&gt;Service "OracleAgentORA01V" create SUCCESS&lt;/pre&gt;The above will create a virtual agent service named OracleAgentORA01V which will be "bound" to ORA01V virtual IP and use d:\agent10g as a location for virtual agent's state files. Note that I'm using port 1830 since port 3872 is used by a "real" agent. You can specify &lt;i&gt;AgentListenOnAllNICs=FALSE&lt;/i&gt; in your &lt;i&gt;emd.properties&lt;/i&gt; file (for all agents in the cluster) if you want virtual and real agents share the same port as this will stop agents from trying to listen on all network adapters on the node.&lt;br /&gt;&lt;br /&gt;Secure the agent in case your OMS is running in the secure mode:&lt;br /&gt;&lt;pre&gt;C:\&gt;d:\agent10g/bin/emctl secure agent&lt;br /&gt;Oracle Enterprise Manager 10g Release 5 Grid Control 10.2.0.5.0.&lt;br /&gt;Copyright (c) 1996, 2009 Oracle Corporation.  All rights reserved.&lt;br /&gt;Agent is already stopped...   Done.&lt;br /&gt;Securing agent...   Started.&lt;br /&gt;Enter Agent Registration Password :&lt;br /&gt;Securing agent...   Successful.&lt;/pre&gt;&lt;b&gt;Deploy virtual agent on the passive node&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;The same deployment command won't work on a passive node simply because drive D: is not there. As a workaround which will allow us to create a virtual agent service on the passive node we will use a local drive C: for initial deployment:&lt;br /&gt;&lt;pre&gt;C:\&gt;emctl deploy agent -n OracleAgentORA01V c:\agent10g ORA01V:1830 ORA01B:1830&lt;br /&gt;Creating shared install...&lt;br /&gt;Source location: C:\oracle\product\10.2.0\agent10g&lt;br /&gt;Destination (shared install) : c:\agent10g&lt;br /&gt;DeployMode : agent&lt;br /&gt;&lt;br /&gt;Creating directories...&lt;br /&gt;Creating targets.xml...&lt;br /&gt;Creating emctl control program...&lt;br /&gt;Creating emtgtctl control program...&lt;br /&gt;Setting log and trace files locations for Agent ...&lt;br /&gt;Secure agent found. New agent should be configured for secure mode&lt;br /&gt;&lt;br /&gt;Source Agent operating in secure mode.&lt;br /&gt;Run "c:\agent10g/bin/emctl secure agent" to secure agent&lt;br /&gt;Service "OracleAgentORA01V" create SUCCESS&lt;/pre&gt;However, this is not what we want as all virtual agents should be sharing the same cluster drive D: instead. To fix the location of the agent state directory, launch &lt;i&gt;regedit.exe&lt;/i&gt; and navigate to &lt;pre&gt;HKLM\SOFTWARE\ORACLE\SYSMAN\OracleAgentORA01V&lt;/pre&gt; registry key. Under that key you'll find &lt;i&gt;EMSTATE&lt;/i&gt; field with &lt;i&gt;c:\agent10g&lt;/i&gt; as its value. Modify this value to be &lt;i&gt;d:\agent10g&lt;/i&gt; instead. You can remove original folder as well.&lt;br /&gt;&lt;br /&gt;Done! I found that this virtual agent will be fully operational once the group failovers to the passive node (don't forget to create a cluster resource for a virtual agent) and will be using shared state directory.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-2584198534657294?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/08/how-to-install-oracle-grid-control.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-3803363569469556426</guid><pubDate>Sun, 08 Feb 2009 04:40:00 +0000</pubDate><atom:updated>2009-02-08T00:59:14.335-05:00</atom:updated><title>Consistent gets from cache (fastpath) 2</title><description>Not so long time ago I wrote an article about interesting optimization in 11G which appears as &lt;a href="http://afatkulin.blogspot.com/2009/01/consistent-gets-from-cache-fastpath.html"&gt;consistent gets from cache (fastpath)&lt;/a&gt;. One thing I've pointed there is that this optimization can operate only if we're accessing the same block over and over again. This may bring us to some interesting observations how certain type of queries are behaving in 11G.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;HASH GROUP BY vs SORT GROUP BY&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Consider the following example:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table dept&lt;br /&gt;  2  (&lt;br /&gt;  3   dept_id  number primary key,&lt;br /&gt;  4   dept_name varchar2(100)&lt;br /&gt;  5  ) organization index;&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt; &lt;br /&gt;SQL&gt; insert /*+ append */ into dept&lt;br /&gt;  2   select level, dbms_random.string('x', 100)&lt;br /&gt;  3    from dual&lt;br /&gt;  4    connect by level &lt;= 10000;&lt;br /&gt; &lt;br /&gt;10000 rows inserted&lt;br /&gt; &lt;br /&gt;SQL&gt; create table emp&lt;br /&gt;  2  (&lt;br /&gt;  3   emp_id  number primary key,&lt;br /&gt;  4   dept_id  references dept (dept_id),&lt;br /&gt;  5   emp_name varchar2(100)&lt;br /&gt;  6  );&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt; &lt;br /&gt;SQL&gt; insert /*+ append */ into emp&lt;br /&gt;  2   select level, trunc(dbms_random.value(1, 10000)), dbms_random.string('x', 100)&lt;br /&gt;  3    from dual&lt;br /&gt;  4    connect by level &lt;= 100000;&lt;br /&gt; &lt;br /&gt;100000 rows inserted&lt;br /&gt; &lt;br /&gt;SQL&gt; commit;&lt;br /&gt; &lt;br /&gt;Commit complete&lt;br /&gt;&lt;br /&gt;SQL&gt; exec dbms_stats.gather_table_stats(user, 'dept');&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;br /&gt; &lt;br /&gt;SQL&gt; exec dbms_stats.gather_table_stats(user, 'emp');&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;Let's say we want to output department names along with how many employees are there:&lt;br /&gt;&lt;pre&gt;SQL&gt; select /*+ gather_plan_statistics */ count(*)&lt;br /&gt;  2  from (&lt;br /&gt;  3   select /*+ no_merge */ d.dept_name, count(*) cnt&lt;br /&gt;  4    from emp e, dept d&lt;br /&gt;  5    where e.dept_id=d.dept_id&lt;br /&gt;  6    group by d.dept_name&lt;br /&gt;  7  );&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;      9999&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID  djysxbcmwwxj3, child number 0&lt;br /&gt;-------------------------------------&lt;br /&gt;select /*+ gather_plan_statistics */ count(*) from (  select /*+&lt;br /&gt;no_merge */ d.dept_name, count(*) cnt   from emp e, dept d   where&lt;br /&gt;e.dept_id=d.dept_id   group by d.dept_name )&lt;br /&gt;&lt;br /&gt;Plan hash value: 1432452646&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation               | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT        |                   |      1 |        |      1 |00:00:00.08 |   11636 |       |       |          |&lt;br /&gt;|   1 |  SORT AGGREGATE         |                   |      1 |      1 |      1 |00:00:00.08 |   11636 |       |       |          |&lt;br /&gt;|   2 |   VIEW                  |                   |      1 |   9999 |   9999 |00:00:00.06 |   11636 |       |       |          |&lt;br /&gt;|   3 |    HASH GROUP BY        |                   |      1 |   9999 |   9999 |00:00:00.06 |   11636 |  2058K|   999K| 2497K (0)|&lt;br /&gt;|   4 |     NESTED LOOPS        |                   |      1 |   9999 |   9999 |00:00:00.02 |   11636 |       |       |          |&lt;br /&gt;|   5 |      VIEW               | VW_GBC_10         |      1 |   9999 |   9999 |00:00:00.02 |    1635 |       |       |          |&lt;br /&gt;|   6 |       HASH GROUP BY     |                   |      1 |   9999 |   9999 |00:00:00.02 |    1635 |  1207K|  1207K| 2496K (0)|&lt;br /&gt;|   7 |        TABLE ACCESS FULL| EMP               |      1 |    100K|    100K|00:00:00.01 |    1635 |       |       |          |&lt;br /&gt;|*  8 |      INDEX UNIQUE SCAN  | SYS_IOT_TOP_15648 |   9999 |      1 |   9999 |00:00:00.01 |   10001 |       |       |          |&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;----------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   8 - access("ITEM_1"="D"."DEPT_ID")&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;27 rows selected.&lt;/pre&gt;Note that 11G did "Group by Placement" automatically (Jonathan Lewis wrote an &lt;a href="http://jonathanlewis.wordpress.com/2008/12/21/group-by/"&gt;article&lt;/a&gt; about it) and our query performed 11636 consistent gets.&lt;br /&gt;&lt;br /&gt;Now, take a look at what happens if we rewrite the query to use a &lt;span style="font-style:italic;"&gt;sort group by&lt;/span&gt; instead:&lt;br /&gt;&lt;pre&gt;SQL&gt; select /*+ gather_plan_statistics */ count(*)&lt;br /&gt;  2  from (&lt;br /&gt;  3   with e as&lt;br /&gt;  4   (&lt;br /&gt;  5    select dept_id, count(*) cnt&lt;br /&gt;  6     from emp e&lt;br /&gt;  7     group by dept_id&lt;br /&gt;  8     order by dept_id&lt;br /&gt;  9   )&lt;br /&gt; 10   select /*+ no_merge */ d.dept_name, e.cnt&lt;br /&gt; 11    from e, dept d&lt;br /&gt; 12    where e.dept_id=d.dept_id&lt;br /&gt; 13  );&lt;br /&gt;&lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;      9999&lt;br /&gt;&lt;br /&gt;SQL&gt; select * from table(dbms_xplan.display_cursor(null,null,'allstats last'));&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;SQL_ID  2utq5vammnwa4, child number 0&lt;br /&gt;-------------------------------------&lt;br /&gt;select /*+ gather_plan_statistics */ count(*) from (  with e as  (&lt;br /&gt;select dept_id, count(*) cnt    from emp e    group by dept_id    order&lt;br /&gt;by dept_id  )  select /*+ no_merge */ d.dept_name, e.cnt   from e, dept&lt;br /&gt;d   where e.dept_id=d.dept_id )&lt;br /&gt;&lt;br /&gt;Plan hash value: 2732217545&lt;br /&gt;&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation              | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |  OMem |  1Mem | Used-Mem |&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT       |                   |      1 |        |      1 |00:00:00.10 |    2624 |       |       |          |&lt;br /&gt;|   1 |  SORT AGGREGATE        |                   |      1 |      1 |      1 |00:00:00.10 |    2624 |       |       |          |&lt;br /&gt;|   2 |   VIEW                 |                   |      1 |   9999 |   9999 |00:00:00.08 |    2624 |       |       |          |&lt;br /&gt;|   3 |    NESTED LOOPS        |                   |      1 |   9999 |   9999 |00:00:00.08 |    2624 |       |       |          |&lt;br /&gt;|   4 |     VIEW               |                   |      1 |   9999 |   9999 |00:00:00.08 |    1635 |       |       |          |&lt;br /&gt;|   5 |      SORT GROUP BY     |                   |      1 |   9999 |   9999 |00:00:00.08 |    1635 |   549K|   549K|  487K (0)|&lt;br /&gt;|   6 |       TABLE ACCESS FULL| EMP               |      1 |    100K|    100K|00:00:00.01 |    1635 |       |       |          |&lt;br /&gt;|*  7 |     INDEX UNIQUE SCAN  | SYS_IOT_TOP_15648 |   9999 |      1 |   9999 |00:00:00.01 |     989 |       |       |          |&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;PLAN_TABLE_OUTPUT&lt;br /&gt;---------------------------------------------------------------------------------------------------------------------------------&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   7 - access("E"."DEPT_ID"="D"."DEPT_ID")&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;27 rows selected.&lt;/pre&gt;What I did there is performed the same transformation myself but replaced &lt;span style="font-style:italic;"&gt;hash group by&lt;/span&gt; with a &lt;span style="font-style:italic;"&gt;sort group by&lt;/span&gt;. Note more than 4 times decrease in the amount of consistent gets. By looking at the plan statistics, you can tell why. Although both group by's performed the same amount of consistent gets (1635), it is a nested loops join with &lt;span style="font-style:italic;"&gt;dep&lt;/span&gt; which produced all the difference -- 10001 for &lt;span style="font-style:italic;"&gt;hash group by&lt;/span&gt; versus only 989 for a &lt;span style="font-style:italic;"&gt;sort group by&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Unless you've heard about &lt;span style="font-style:italic;"&gt;consistent gets from cache (fastpath)&lt;/span&gt; optimization, the above results may produce quite a bit of surprise for you. Note that due to a sorting, which has to be performed by our second query, the first query still performs better. However, in the environments which are wreaking havoc on CBC latches, the additional savings on number of consistent gets may have a potential to alleviate additional sorting expenses. In other words, the lesser are expenses for outer resultset sorting, the more appealing this could be.&lt;br /&gt;&lt;br /&gt;Of course, before you even consider this as an optimization opportunity, keep in mind that you're relying on a specific feature which may narrow you down to a specific dot releases (or even patches) as it may change (or even completely disappear) in the next versions.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-3803363569469556426?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/02/consistent-gets-from-cache-fastpath-2.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8597647665912053627</guid><pubDate>Sun, 01 Feb 2009 20:18:00 +0000</pubDate><atom:updated>2009-02-02T04:50:07.454-05:00</atom:updated><title>AE enqueue</title><description>Just a quick follow up from my &lt;a href="http://afatkulin.blogspot.com/2009/01/edition-based-redifinition-speculation.html"&gt;previous&lt;/a&gt; post.&lt;br /&gt;&lt;br /&gt;The relation of AE enqueue to editions was mentioned a couple of times around the internet already. Any user session connected to a database holds AE enqueue in a shared mode...&lt;br /&gt;&lt;pre&gt;SQL&gt; select type, id1, lmode, sys_context('userenv', 'current_edition_id') edition_id&lt;br /&gt;  2   from v$lock&lt;br /&gt;  3   where type='AE'&lt;br /&gt;  4    and sid=sys_context('userenv', 'sid');&lt;br /&gt; &lt;br /&gt;TYPE        ID1      LMODE EDITION_ID&lt;br /&gt;---- ---------- ---------- --------------------------------------------------------------------------------&lt;br /&gt;AE          100          4 100&lt;/pre&gt;...and the first argument seems to be session's &lt;span style="font-style:italic;"&gt;current_edition_id&lt;/span&gt;. I guess the lock mode will require an upgrade to exclusive mode during edition alterations.&lt;br /&gt;&lt;br /&gt;Here is another interesting thing -- it looks like installing 11.1.0.7 patchset increments the &lt;span style="font-style:italic;"&gt;current_edition_id&lt;/span&gt;. The value in 11.1.0.6 seems to be 99, but 11.1.0.7 changes it to 100 (you can observe it in &lt;span style="font-style:italic;"&gt;sys.editon$&lt;/span&gt; table as well). Does that mean that Oracle has any plans in doing patchset installation through edition-based redefinition (install the patchset online, short downtime is required only during switch to an upgraded edition) or is it simply a way to represent version change?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Updated the same day:&lt;/span&gt; it looks like in case your database was &lt;span style="font-style:italic;"&gt;upgraded&lt;/span&gt; from a previous release, &lt;span style="font-style:italic;"&gt;edition_id&lt;/span&gt; for &lt;span style="font-style:italic;"&gt;ORA$BASE&lt;/span&gt; will be some other number as it represents &lt;span style="font-style:italic;"&gt;ORA$BASE's object_id&lt;/span&gt;. This also means that my initial assumption about patchset installation changing &lt;span style="font-style:italic;"&gt;edition_id&lt;/span&gt; is not correct as it is just whatever &lt;span style="font-style:italic;"&gt;object_id&lt;/span&gt; is being available at the time. Before &lt;span style="font-style:italic;"&gt;ORA$BASE&lt;/span&gt; edition is being created, 11.1.0.7 creates one more object (compared to 11.1.0.6), index &lt;span style="font-style:italic;"&gt;I_SYN2&lt;/span&gt;, which explains advance in edition_id.&lt;br /&gt;&lt;br /&gt;However, the idea seems to be interesting anyway...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8597647665912053627?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/02/ae-enqueue.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-1926758158673415212</guid><pubDate>Sat, 31 Jan 2009 22:44:00 +0000</pubDate><atom:updated>2009-02-01T03:02:54.962-05:00</atom:updated><title>Edition-based redifinition (a speculation)</title><description>As you can see from &lt;a href="http://download.oracle.com/docs/cd/B28359_01/readmes.111/b28280/toc.htm#BABGIGDC"&gt;this link&lt;/a&gt;, there is a feature called &lt;span style="font-style:italic;"&gt;Edition-based redefinition&lt;/span&gt; mentioned in the 11GR1 documentation. Unfortunately, all it says is that this feature is unavailable as of now so there is really not much you can tell about it.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;A speculation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'm usually not a great fun of doing any sort of a guesswork, however, in this case it might be an interesting exercise to observe some bits and pieces presented in the current release of 11GR1 in order to see whether it can give us some clues as to what expect from this new feature as well as what the potential underpinning might look like. As well as prepare yourself for some implications...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;SYS.OBJ$&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Perhaps this will be the first thing what you'll notice as soon as 11G's data dictionary is concerned. For example, this is how &lt;span style="font-style:italic;"&gt;dba_synonyms&lt;/span&gt; view is defined in 10GR2:&lt;br /&gt;&lt;pre&gt;create or replace view dba_synonyms&lt;br /&gt;(owner, synonym_name, table_owner, table_name, db_link)&lt;br /&gt;as&lt;br /&gt;select u.name, o.name, s.owner, s.name, s.node&lt;br /&gt;from sys.user$ u, sys.syn$ s, sys.obj$ o&lt;br /&gt;where o.obj# = s.obj#&lt;br /&gt;  and o.type# = 5&lt;br /&gt;  and o.owner# = u.user#;&lt;/pre&gt;Now, take a look at the same view's definition in 11GR1:&lt;br /&gt;&lt;pre&gt;create or replace view dba_synonyms&lt;br /&gt;(owner, synonym_name, table_owner, table_name, db_link)&lt;br /&gt;as&lt;br /&gt;select u.name, o.name, s.owner, s.name, s.node&lt;br /&gt;from sys.user$ u, sys.syn$ s, &lt;font color="blue"&gt;sys."_CURRENT_EDITION_OBJ"&lt;/font&gt; o&lt;br /&gt;where o.obj# = s.obj#&lt;br /&gt;  and o.type# = 5&lt;br /&gt;  and o.owner# = u.user#;&lt;/pre&gt;For you see, the reference to &lt;span style="font-style:italic;"&gt;SYS.OBJ$&lt;/span&gt; was replaced with &lt;span style="font-style:italic;"&gt;SYS."_CURRENT_EDITION_OBJ"&lt;/span&gt;. This replacement occurs all around the place in the data dictionary which alone makes it interesting enough to attract a bit of attention.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;SYS._CURRENT_EDITION_OBJ&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What is &lt;span style="font-style:italic;"&gt;_CURRENT_EDITION_OBJ&lt;/span&gt;? It's a view:&lt;br /&gt;&lt;pre&gt;SQL&gt; select object_type&lt;br /&gt;  2   from dba_objects&lt;br /&gt;  3   where object_name='_CURRENT_EDITION_OBJ';&lt;br /&gt; &lt;br /&gt;OBJECT_TYPE&lt;br /&gt;-------------------&lt;br /&gt;VIEW&lt;/pre&gt;Let's take a look at this view's definition (I've omitted fields list for the sake of clarity):&lt;br /&gt;&lt;pre&gt;select ...&lt;br /&gt;from obj$ o, user$ u&lt;br /&gt;where o.owner# = u.user#&lt;br /&gt;  and (   /* non-versionable object */&lt;br /&gt;          (   o.type# not in (4,5,7,8,9,10,11,12,13,14,22,87,88)&lt;br /&gt;           or bitand(u.spare1, 16) = 0)&lt;br /&gt;          /* versionable object visible in current edition */&lt;br /&gt;       or (    o.type# in (4,5,7,8,9,10,11,12,13,14,22,87)&lt;br /&gt;           and (   (u.type# &lt;&gt; 2 and&lt;br /&gt;                    sys_context('userenv', 'current_edition_name') = 'ORA$BASE')&lt;br /&gt;                or (u.type# = 2 and&lt;br /&gt;                    u.spare2 = sys_context('userenv', 'current_edition_id'))&lt;br /&gt;                or exists (select 1 from obj$ o2, user$ u2&lt;br /&gt;                           where o2.type# = 88&lt;br /&gt;                             and o2.dataobj# = o.obj#&lt;br /&gt;                             and o2.owner# = u2.user#&lt;br /&gt;                             and u2.type#  = 2&lt;br /&gt;                             and u2.spare2 =&lt;br /&gt;                                  sys_context('userenv', 'current_edition_id'))&lt;br /&gt;               )&lt;br /&gt;          )&lt;br /&gt;      );&lt;/pre&gt;First of all, it mentions about (A) versionable objects and (B) current edition which is referenced through session's context &lt;span style="font-style:italic;"&gt;sys_context('userenv', 'current_edition_id')&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Versionable objects&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Only the objects of the following types seems to be versionable:&lt;br /&gt;&lt;ul&gt;&lt;li/&gt;VIEW&lt;br /&gt;&lt;li/&gt;SYNONYM&lt;br /&gt;&lt;li/&gt;PROCEDURE&lt;br /&gt;&lt;li/&gt;FUNCTION&lt;br /&gt;&lt;li/&gt;PACKAGE&lt;br /&gt;&lt;li/&gt;PACKAGE BODY&lt;br /&gt;&lt;li/&gt;TRIGGER&lt;br /&gt;&lt;li/&gt;TYPE&lt;br /&gt;&lt;li/&gt;TYPE BODY&lt;br /&gt;&lt;li/&gt;LIBRARY&lt;br /&gt;&lt;li/&gt;ASSEMBLY&lt;/ul&gt;Object &lt;span style="font-style:italic;"&gt;type# = 88&lt;/span&gt; seems to be something special as I couldn't find what it is from &lt;span style="font-style:italic;"&gt;sql.bsq&lt;/span&gt; (nor d&lt;span style="font-style:italic;"&gt;ba_objects&lt;/span&gt;) and it is being referenced using somewhat special way: if &lt;span style="font-style:italic;"&gt;obj#&lt;/span&gt; matches &lt;span style="font-style:italic;"&gt;dataobj#&lt;/span&gt; of some entry with &lt;span style="font-style:italic;"&gt;type# = 88&lt;/span&gt; and this entry is part if the current edition, the object is considered to be a part of the current edition as well. Note that the join itself permits this special object to exist in some other schema, though I don't know whether this has any practical meaning or not.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;New userenv attributes&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are two (at least) new userenv context attributes:&lt;br /&gt;&lt;pre&gt;SQL&gt; select sys_context('userenv', 'current_edition_name')&lt;br /&gt;  2   current_edition_name from dual;&lt;br /&gt; &lt;br /&gt;CURRENT_EDITION_NAME&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;ORA$BASE&lt;br /&gt;SQL&gt; select sys_context('userenv', 'current_edition_id')&lt;br /&gt;  2   current_edition_id from dual;&lt;br /&gt; &lt;br /&gt;CURRENT_EDITION_ID&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;100&lt;/pre&gt;Changing these will change data returned by &lt;span style="font-style:italic;"&gt;_CURRENT_EDITION_OBJ&lt;/span&gt; view as well and, given that that view is referenced instead of &lt;span style="font-style:italic;"&gt;obj$&lt;/span&gt;, it looks like these contexts will be the primary mechanism responsible for referencing one version (edition) of the object or the other.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;New user type&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Again, by looking at the view definition, we may spot that versionable objects are being concerned as far as &lt;span style="font-style:italic;"&gt;user$.type# = 2&lt;/span&gt;. To remind you, 0 is a role and 1 is a regular user, so there will be something new, perhaps this new feature could be enabled on a per-user level and/or versioned objects will be owned by some other "ghost" schema (my comment about object with type 88 seems to be allowing this, at least technically).&lt;br /&gt;&lt;br /&gt;Some interesting clues might be get from &lt;span style="font-style:italic;"&gt;_CURRENT_EDITION_OBJ&lt;/span&gt;'s field definition itself:&lt;br /&gt;&lt;pre&gt;when (u.type# = 2) then&lt;br /&gt; (select eo.name from obj$ eo where eo.obj# = u.spare2)&lt;br /&gt;else&lt;br /&gt; 'ORA$BASE'&lt;br /&gt;end&lt;/pre&gt;The objects which are owned by this new user type will have &lt;span style="font-style:italic;"&gt;editition_id&lt;/span&gt; as their &lt;span style="font-style:italic;"&gt;object_id&lt;/span&gt; and will be named after edition name. That also means that &lt;span style="font-style:italic;"&gt;object_id is no longer unique&lt;/span&gt;:&lt;pre&gt;create unique index i_obj1 on obj$(obj#, owner#, type#) (11GR1)&lt;br /&gt;create unique index i_obj1 on obj$(obj#) (10GR2)&lt;br /&gt;(from sql.bsq)&lt;/pre&gt;I wander whether this have some chances to break legacy code which references &lt;span style="font-style:italic;"&gt;sys.obj$&lt;/span&gt; by &lt;span style="font-style:italic;"&gt;obj#&lt;/span&gt; and expect one row to be returned at most...&lt;br /&gt;&lt;br /&gt;Does all that, again, points out that object versions will be kept under this special user and will be linked back using &lt;span style="font-style:italic;"&gt;data_object_id&lt;/span&gt;? Is this also a reason why objects which do have "proper" &lt;span style="font-style:italic;"&gt;data_object_id&lt;/span&gt; are not versionable?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;A speculation (again)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Pleases note that everything said in this post is based on some (random) observations of preliminary data available in the current release (11.1.0.7). There are chances that some (if not all) of this data may became altered or even completely invalid when this feature will be officially announced.&lt;br /&gt;&lt;br /&gt;However, one thing seems to be clear -- in order to support this new feature, some serious alterations of the data dictionary are taking place and you should keep yourself alerted...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-1926758158673415212?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/edition-based-redifinition-speculation.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-1705425970408491422</guid><pubDate>Sat, 31 Jan 2009 21:59:00 +0000</pubDate><atom:updated>2009-01-31T17:38:55.494-05:00</atom:updated><title>Create database or who wants some DMT?</title><description>&lt;span style="font-weight:bold;"&gt;Simpler than ever&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Starting from Oracle 10G, creating the database can be as simple as this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create database;&lt;br /&gt;&lt;br /&gt;Database created.&lt;/pre&gt;It's not a surprise that this feature was somewhat advertised here and there. However, what was missing in these advertisements is this:&lt;br /&gt;&lt;pre&gt;SQL&gt; select name, decode(bitmapped, 0, 'DMT', 'LMT')&lt;br /&gt;      from ts$&lt;br /&gt;      order by name;  2    3&lt;br /&gt;&lt;br /&gt;NAME                           DEC&lt;br /&gt;------------------------------ ---&lt;br /&gt;SYSAUX                         LMT&lt;br /&gt;&lt;span style="color:red;"&gt;SYSTEM                         DMT&lt;/span&gt;&lt;br /&gt;SYS_UNDOTS                     LMT&lt;/pre&gt;For you see, &lt;span style="font-style:italic;"&gt;create database&lt;/span&gt; will make your &lt;span style="font-style:italic;"&gt;SYSTEM&lt;/span&gt; tablespace to be &lt;span style="font-style:italic;"&gt;dictionary managed&lt;/span&gt; by default. I don't really know if there are any reasons for this and since everything else will default to LMT, this should not be a big deal for most of you anyway. Just don't forget that &lt;span style="font-style:italic;"&gt;SYSTEM&lt;/span&gt; hosts objects like &lt;span style="font-style:italic;"&gt;AUD$&lt;/span&gt; (audit log), &lt;span style="font-style:italic;"&gt;FGA_LOG$&lt;/span&gt; (fine-grained audit log) or &lt;span style="font-style:italic;"&gt;NCOMP_DLL$&lt;/span&gt; (natively compiled objects) which could grow to a fairly large number of extents.&lt;br /&gt;&lt;br /&gt;I was a bit surprised watching this relic appear even when you do this in 11.1.0.7...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-1705425970408491422?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/create-database-or-who-wants-some-dmt.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8564059587898722413</guid><pubDate>Sat, 31 Jan 2009 03:27:00 +0000</pubDate><atom:updated>2009-01-31T01:36:02.065-05:00</atom:updated><title>Moving a datafile</title><description>Sometimes you need to move a datafile into a different mount point or ASM diskgroup. This could make you wandering what technique you can use in order to minimize downtime. I'll show you one of my favorite methods which works well under certain circumstances.&lt;br /&gt;&lt;br /&gt;Let's say you want to move the following datafile...&lt;br /&gt;&lt;pre style="font-size:11px;"&gt;SQL&gt; select file_id, file_name&lt;br /&gt;  2     from dba_data_files&lt;br /&gt;  3     where tablespace_name='USERS';&lt;br /&gt;&lt;br /&gt;FILE_ID FILE_NAME&lt;br /&gt;------- --------------------------------------------------------&lt;br /&gt;      4 /u01/oradata/ORA11GR1/datafile/o1_mf_users_4q759m64_.dbf&lt;/pre&gt;...into mount point /u02.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Backup as copy&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The first thing we need to do is backup this datafile as copy using RMAN:&lt;br /&gt;&lt;pre style="font-size:11px;"&gt;[oracle@ora11gr1a ~]$ rman&lt;br /&gt;&lt;br /&gt;Recovery Manager: Release 11.1.0.7.0 - Production on Thu Jan 29 17:29:59 2009&lt;br /&gt;&lt;br /&gt;Copyright (c) 1982, 2007, Oracle.  All rights reserved.&lt;br /&gt;&lt;br /&gt;RMAN&gt; connect target;&lt;br /&gt;&lt;br /&gt;connected to target database: ORA11GR1 (DBID=3707369966)&lt;br /&gt;&lt;br /&gt;RMAN&gt; backup as copy datafile 4&lt;br /&gt;2&gt;      format '/u02/oradata/ORA11GR1/datafile/users01.dbf';&lt;br /&gt;&lt;br /&gt;Starting backup at 29-JAN-09&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;channel ORA_DISK_1: starting datafile copy&lt;br /&gt;input datafile file number=00004 name=/u01/oradata/ORA11GR1/datafile/o1_mf_users&lt;br /&gt;_4q759m64_.dbf&lt;br /&gt;output file name=/u02/oradata/ORA11GR1/datafile/users01.dbf tag=TAG20090129T1825&lt;br /&gt;32 RECID=14 STAMP=677442334&lt;br /&gt;channel ORA_DISK_1: datafile copy complete, elapsed time: 00:00:03&lt;br /&gt;Finished backup at 29-JAN-09&lt;/pre&gt;&lt;span style="font-weight:bold;"&gt;Rollforward image copy&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Since switching to datafile copy will require datafile recover, it might be a good idea to rollforward this image copy first, in order to bring it up to date...&lt;br /&gt;&lt;pre style="font-size:11px;"&gt;RMAN&gt; list copy of datafile 4;&lt;br /&gt;&lt;br /&gt;List of Datafile Copies&lt;br /&gt;=======================&lt;br /&gt;&lt;br /&gt;Key     File S Completion Time Ckp SCN    Ckp Time&lt;br /&gt;------- ---- - --------------- ---------- ---------------&lt;br /&gt;14      4    A 29-JAN-09       2033489    29-JAN-09&lt;br /&gt;        Name: /u02/oradata/ORA11GR1/datafile/users01.dbf&lt;br /&gt;        Tag: TAG20090129T182532&lt;br /&gt;&lt;br /&gt;RMAN&gt; backup incremental from scn 2033489 datafile 4 format '/u02/oradata/ORA11G&lt;br /&gt;R1/datafile/%U';&lt;br /&gt;&lt;br /&gt;Starting backup at 29-JAN-09&lt;br /&gt;&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;backup will be obsolete on date 05-FEB-09&lt;br /&gt;archived logs will not be kept or backed up&lt;br /&gt;channel ORA_DISK_1: starting full datafile backup set&lt;br /&gt;channel ORA_DISK_1: specifying datafile(s) in backup set&lt;br /&gt;input datafile file number=00004 name=/u01/oradata/ORA11GR1/datafile/o1_mf_users&lt;br /&gt;_4q759m64_.dbf&lt;br /&gt;channel ORA_DISK_1: starting piece 1 at 29-JAN-09&lt;br /&gt;channel ORA_DISK_1: finished piece 1 at 29-JAN-09&lt;br /&gt;piece handle=/u02/oradata/ORA11GR1/datafile/1nk61t1d_1_1 tag=TAG20090129T183004&lt;br /&gt;comment=NONE&lt;br /&gt;channel ORA_DISK_1: backup set complete, elapsed time: 00:00:01&lt;br /&gt;&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;backup will be obsolete on date 05-FEB-09&lt;br /&gt;archived logs will not be kept or backed up&lt;br /&gt;channel ORA_DISK_1: starting full datafile backup set&lt;br /&gt;channel ORA_DISK_1: specifying datafile(s) in backup set&lt;br /&gt;including current control file in backup set&lt;br /&gt;channel ORA_DISK_1: starting piece 1 at 29-JAN-09&lt;br /&gt;channel ORA_DISK_1: finished piece 1 at 29-JAN-09&lt;br /&gt;piece handle=/u02/oradata/ORA11GR1/datafile/1ok61t1e_1_1 tag=TAG20090129T183004&lt;br /&gt;comment=NONE&lt;br /&gt;channel ORA_DISK_1: backup set complete, elapsed time: 00:00:01&lt;br /&gt;Finished backup at 29-JAN-09&lt;br /&gt;&lt;br /&gt;RMAN&gt; recover copy of datafile 4;&lt;br /&gt;&lt;br /&gt;Starting recover at 29-JAN-09&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;channel ORA_DISK_1: starting incremental datafile backup set restore&lt;br /&gt;channel ORA_DISK_1: specifying datafile copies to recover&lt;br /&gt;recovering datafile copy file number=00004 name=/u02/oradata/ORA11GR1/datafile/u&lt;br /&gt;sers01.dbf&lt;br /&gt;channel ORA_DISK_1: reading from backup piece /u02/oradata/ORA11GR1/datafile/1nk&lt;br /&gt;61t1d_1_1&lt;br /&gt;channel ORA_DISK_1: piece handle=/u02/oradata/ORA11GR1/datafile/1nk61t1d_1_1 tag&lt;br /&gt;=TAG20090129T183004&lt;br /&gt;channel ORA_DISK_1: restored backup piece 1&lt;br /&gt;channel ORA_DISK_1: restore complete, elapsed time: 00:00:01&lt;br /&gt;Finished recover at 29-JAN-09&lt;br /&gt;&lt;br /&gt;RMAN&gt; list copy of datafile 4;&lt;br /&gt;&lt;br /&gt;List of Datafile Copies&lt;br /&gt;=======================&lt;br /&gt;&lt;br /&gt;Key     File S Completion Time Ckp SCN    Ckp Time&lt;br /&gt;------- ---- - --------------- ---------- ---------------&lt;br /&gt;15      4    A 29-JAN-09       2033633    29-JAN-09&lt;br /&gt;        Name: /u02/oradata/ORA11GR1/datafile/users01.dbf&lt;br /&gt;        Tag: TAG20090129T182532&lt;br /&gt;&lt;/pre&gt;Note that image copy's checkpoint SCN has moved forward. Keep in mind that this step generally makes sense only if you have block change tracking enabled and/or there is a huge amount of archivelogs to apply, as it will be a trade-off between creating and applying the incremental backup compared to directly applying all necessarily archivelogs. This step can be done starting from Oracle 10G.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Switch datafile&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;All we have to do now is execute small RMAN block...&lt;br /&gt;&lt;pre style="font-size:11px;"&gt;RMAN&gt; run&lt;br /&gt;2&gt; {&lt;br /&gt;3&gt;      sql 'alter database datafile 4 offline';&lt;br /&gt;4&gt;      switch datafile 4 to datafilecopy '/u02/oradata/ORA11GR1/datafile/users0&lt;br /&gt;1.dbf';&lt;br /&gt;5&gt;      recover datafile 4;&lt;br /&gt;6&gt;      sql 'alter database datafile 4 online';&lt;br /&gt;7&gt; }&lt;br /&gt;&lt;br /&gt;sql statement: alter database datafile 4 offline&lt;br /&gt;&lt;br /&gt;datafile 4 switched to datafile copy&lt;br /&gt;input datafile copy RECID=15 STAMP=677442659 file name=/u02/oradata/ORA11GR1/dat&lt;br /&gt;afile/users01.dbf&lt;br /&gt;&lt;br /&gt;Starting recover at 29-JAN-09&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;&lt;br /&gt;starting media recovery&lt;br /&gt;media recovery complete, elapsed time: 00:00:00&lt;br /&gt;&lt;br /&gt;Finished recover at 29-JAN-09&lt;br /&gt;&lt;br /&gt;sql statement: alter database datafile 4 online&lt;/pre&gt; This is where you'll have some downtime. The amount of downtime depends on how long it will take to recover the datafile which will generally be a function of how many archivelogs needs to be applied which, in turn, can be reduced by using incremental backup. The point is that this step can be really fast.&lt;br /&gt;&lt;br /&gt;Don't forget to watch against nologging operations!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8564059587898722413?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/moving-datafile.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-6018804904447102634</guid><pubDate>Sun, 25 Jan 2009 13:27:00 +0000</pubDate><atom:updated>2009-01-25T10:07:08.011-05:00</atom:updated><title>Update and rownum oddity</title><description>Take a look at the following table:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table codes&lt;br /&gt;  2  (&lt;br /&gt;  3     code varchar2(10),&lt;br /&gt;  4     used number,&lt;br /&gt;  5     constraint pk_codes primary key (used, code)&lt;br /&gt;  6  ) organization index;&lt;br /&gt;&lt;br /&gt;Table created.&lt;br /&gt;&lt;br /&gt;SSQL&gt; insert into codes&lt;br /&gt;  2     select  dbms_random.string('x', 10),&lt;br /&gt;  3             case when level &lt;= 5000 then 1 else 0 end&lt;br /&gt;  4             from dual&lt;br /&gt;  5             connect by level &lt;= 100000;&lt;br /&gt;&lt;br /&gt;100000 rows created.&lt;br /&gt;&lt;br /&gt;SQL&gt; commit;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;br /&gt;&lt;br /&gt;SQL&gt; exec dbms_stats.gather_table_stats(user, 'codes');&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;/pre&gt;This table contains a set of codes with &lt;span style="font-style:italic;"&gt;used&lt;/span&gt; column representing whether the code was already used (1) or not (0). We need to return one (random) unused code from the above table and mark this code as used. This is very easy to archive using the following update statement:&lt;br /&gt;&lt;pre&gt;SQL&gt; variable code varchar2(10);&lt;br /&gt;SQL&gt; set autot traceonly&lt;br /&gt;SQL&gt; update codes set used=1&lt;br /&gt;  2     where used=0 and rownum=1&lt;br /&gt;  3     returning code into :code;&lt;br /&gt;&lt;br /&gt;1 row updated.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 1169687698&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation              | Name     | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;|   0 | UPDATE STATEMENT       |          |     1 |    13 |   117   (1)| 00:00:02 |&lt;br /&gt;|   1 |  UPDATE                | CODES    |       |       |            |          |&lt;br /&gt;|*  2 |   COUNT STOPKEY        |          |       |       |            |          |&lt;br /&gt;|*  3 |    INDEX FAST FULL SCAN| PK_CODES | 50000 |   634K|   117   (1)| 00:00:02 |&lt;br /&gt;-----------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter(ROWNUM=1)&lt;br /&gt;   3 - filter("USED"=0)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          1  recursive calls&lt;br /&gt;          5  db block gets&lt;br /&gt;         12  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;        124  redo size&lt;br /&gt;        913  bytes sent via SQL*Net to client&lt;br /&gt;        874  bytes received via SQL*Net from client&lt;br /&gt;          3  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;/pre&gt;However, the plan is not exactly what I would expect (I'm running this on 11.1.0.7)... Why do &lt;span style="font-style:italic;"&gt;IFFS&lt;/span&gt; when we can do &lt;span style="font-style:italic;"&gt;IRS&lt;/span&gt; to get only one row? This is exactly what regular select does, after all:&lt;br /&gt;&lt;pre&gt;SQL&gt; select *&lt;br /&gt;  2     from codes&lt;br /&gt;  3     where used=0 and rownum=1;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 802332609&lt;br /&gt;&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT  |          |     1 |    13 |     2   (0)| 00:00:01 |&lt;br /&gt;|*  1 |  COUNT STOPKEY    |          |       |       |            |          |&lt;br /&gt;|*  2 |   INDEX RANGE SCAN| PK_CODES |     1 |    13 |     2   (0)| 00:00:01 |&lt;br /&gt;------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - filter(ROWNUM=1)&lt;br /&gt;   2 - access("USED"=0)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          1  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;          2  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;        590  bytes sent via SQL*Net to client&lt;br /&gt;        520  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;10053 trace&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's take a look at 10053 trace output for both &lt;span style="font-style:italic;"&gt;select&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;update&lt;/span&gt; statements. I'm picking up relevant sections.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;UPDATE:&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;***************************************&lt;br /&gt;BASE STATISTICAL INFORMATION&lt;br /&gt;***********************&lt;br /&gt;Table Stats::&lt;br /&gt;  Table: CODES  Alias: CODES&lt;br /&gt;    #Rows: 100000  #Blks:  423  AvgRowLen:  13.00&lt;br /&gt;Index Stats::&lt;br /&gt;  Index: PK_CODES  Col#: 2 1&lt;br /&gt;    LVLS: 1  #LB: 423  #DK: 100000  LB/K: 1.00  DB/K: 1.00  CLUF: 0.00&lt;br /&gt;Access path analysis for CODES&lt;br /&gt;***************************************&lt;br /&gt;SINGLE TABLE ACCESS PATH &lt;br /&gt;  Single Table Cardinality Estimation for CODES[CODES] &lt;br /&gt;  Table: CODES  Alias: CODES&lt;br /&gt;    &lt;font color="red"&gt;Card: Original: 100000.000000  Rounded: 50000  Computed: 50000.00  Non Adjusted: 50000.00&lt;/font&gt;&lt;br /&gt;  Access Path: index (index (FFS))&lt;br /&gt;    Index: PK_CODES&lt;br /&gt;    resc_io: 116.00  resc_cpu: 20012369&lt;br /&gt;    ix_sel: 0.000000  ix_sel_with_filters: 1.000000 &lt;br /&gt;  Access Path: index (FFS)&lt;br /&gt;    Cost:  116.81  Resp: 116.81  Degree: 1&lt;br /&gt;      Cost_io: 116.00  Cost_cpu: 20012369&lt;br /&gt;      Resp_io: 116.00  Resp_cpu: 20012369&lt;br /&gt;OPTIMIZER PERCENT INDEX CACHING = 0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Access Path: index (IndexOnly)&lt;br /&gt;    Index: PK_CODES&lt;br /&gt;    resc_io: 213.00  resc_cpu: 11516867&lt;br /&gt;    ix_sel: 0.500000  ix_sel_with_filters: 0.500000 &lt;br /&gt;    Cost: 213.47  Resp: 213.47  Degree: 1&lt;br /&gt;  Best:: AccessPath: IndexFFS&lt;br /&gt;  Index: PK_CODES&lt;br /&gt;         Cost: 116.81  Degree: 1  Resp: 116.81  Card: 50000.00  Bytes: 0&lt;br /&gt;&lt;br /&gt;***************************************&lt;/pre&gt;&lt;span style="font-style:italic;"&gt;Nothing else&lt;/span&gt; is being tried and this is what optimizer selects as the best execution plan. Note how cardinalities are being reported (highlighted in &lt;font color="red"&gt;red&lt;/font&gt;).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;SELECT:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In addition to the above, has one more section:&lt;br /&gt;&lt;pre&gt;***************************************&lt;br /&gt;SINGLE TABLE ACCESS PATH &lt;font color="blue"&gt;(First K Rows)&lt;/font&gt;&lt;br /&gt;  Single Table Cardinality Estimation for CODES[CODES] &lt;br /&gt;  Table: CODES  Alias: CODES&lt;br /&gt;    &lt;font color="green"&gt;Card: Original: 2.000000  Rounded: 1  Computed: 1.00  Non Adjusted: 1.00&lt;/font&gt;&lt;br /&gt;  Access Path: index (index (FFS))&lt;br /&gt;    Index: PK_CODES&lt;br /&gt;    resc_io: 2.00  resc_cpu: 7461&lt;br /&gt;    ix_sel: 0.000000  ix_sel_with_filters: 1.000000 &lt;br /&gt;  Access Path: index (FFS)&lt;br /&gt;    Cost:  2.00  Resp: 2.00  Degree: 1&lt;br /&gt;      Cost_io: 2.00  Cost_cpu: 7461&lt;br /&gt;      Resp_io: 2.00  Resp_cpu: 7461&lt;br /&gt;OPTIMIZER PERCENT INDEX CACHING = 0&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;  Access Path: index (IndexOnly)&lt;br /&gt;    Index: PK_CODES&lt;br /&gt;    resc_io: 2.00  resc_cpu: 14443&lt;br /&gt;    ix_sel: 0.500000  ix_sel_with_filters: 0.500000 &lt;br /&gt;    Cost: 2.00  Resp: 2.00  Degree: 1&lt;br /&gt;  Best:: AccessPath: IndexRange&lt;br /&gt;  Index: PK_CODES&lt;br /&gt;         Cost: 2.00  Degree: 1  Resp: 2.00  Card: 1.00  Bytes: 13&lt;br /&gt;&lt;br /&gt;First K Rows: unchanged join prefix len = 1&lt;br /&gt;Join order[1]:  CODES[CODES]#0&lt;br /&gt;***********************&lt;/pre&gt;Note how cardinalities has changed (highlighted in &lt;font color="green"&gt;green&lt;/font&gt;) this time. What happened is &lt;span style="font-style:italic;"&gt;rownum = 1&lt;/span&gt; predicate resulted in &lt;span style="font-style:italic;"&gt;fist_rows(1)&lt;/span&gt; mode (highlighted in &lt;font color="blue"&gt;blue&lt;/font&gt;), affecting how cardinalities were calculated.&lt;br /&gt;&lt;br /&gt;We know that &lt;span style="font-style:italic;"&gt;first_rows(n)&lt;/span&gt; hint is being ignored in &lt;span style="font-style:italic;"&gt;update&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;delete&lt;/span&gt; statements, thus our update statement always goes in &lt;span style="font-style:italic;"&gt;all_rows&lt;/span&gt; mode.&lt;br /&gt;&lt;br /&gt;You can confirm that &lt;span style="font-style:italic;"&gt;select&lt;/span&gt; behaves exactly the same way when in &lt;span style="font-style:italic;"&gt;all_rows&lt;/span&gt; mode:&lt;br /&gt;&lt;pre&gt;SQL&gt; select /*+ all_rows */ *&lt;br /&gt;  2     from codes&lt;br /&gt;  3     where used=0 and rownum=1;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2682988822&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT      |          |     1 |    13 |   117   (1)| 00:00:02 |&lt;br /&gt;|*  1 |  COUNT STOPKEY        |          |       |       |            |          |&lt;br /&gt;|*  2 |   INDEX FAST FULL SCAN| PK_CODES | 50000 |   634K|   117   (1)| 00:00:02 |&lt;br /&gt;----------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   1 - filter(ROWNUM=1)&lt;br /&gt;   2 - filter("USED"=0)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;         12  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;        590  bytes sent via SQL*Net to client&lt;br /&gt;        520  bytes received via SQL*Net from client&lt;br /&gt;          2  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;          1  rows processed&lt;/pre&gt;Well, I guess here goes my next wish for &lt;span style="font-style:italic;"&gt;CBO&lt;/span&gt; improvement regarding how &lt;span style="font-style:italic;"&gt;update&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;delete&lt;/span&gt; statements are handled with predicates involving &lt;span style="font-style:italic;"&gt;rownum&lt;/span&gt;...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-6018804904447102634?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/update-and-rownum-oddity.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">9</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-5563627845038955843</guid><pubDate>Fri, 23 Jan 2009 21:29:00 +0000</pubDate><atom:updated>2009-01-24T04:05:28.708-05:00</atom:updated><title>11G Managed Recovery Process</title><description>&lt;span style="font-style:italic;"&gt;MRP&lt;/span&gt; process is commonly &lt;a href="http://www.google.ca/search?hl=en&amp;amp;q=oracle+mrp+process&amp;amp;btnG=Google+Search&amp;amp;meta="&gt;referenced&lt;/a&gt; throughout the web as the process which performs redo apply to your managed standby database. MRP may can in a team with PQ slave (or pr&lt;span style="font-style:italic;"&gt;nn&lt;/span&gt; in 11G) processes in case you start managed recovery in parallel.&lt;br /&gt;&lt;br /&gt;Unfortunately, the term &lt;span style="font-style:italic;"&gt;performs redo apply&lt;/span&gt; seems to be causing some confusion along the way as well. I found it very common that people believes that it's MRP process which performs both reads from the redo streams as well as writes changes into datafiles. For example, sometimes they try to battle slow log apply by increasing managed recovery parallelism without realizing that there is in fact a bit more to the puzzle.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Some details&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'm going to use my &lt;span style="font-weight:bold;"&gt;11G DataGuard&lt;/span&gt; setup to demonstrate a couple of key points. My setup is operating using real time apply (no parallel), which makes the entire example a bit simpler to demonstrate.&lt;br /&gt;&lt;br /&gt;Let's update a row on the source DB:&lt;br /&gt;&lt;pre&gt;SQL&gt; update t set n=n;&lt;br /&gt;&lt;br /&gt;1 row updated.&lt;br /&gt;&lt;br /&gt;SQL&gt;  commit;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;/pre&gt;Now, take a look at MRP strace output which was produced as a result of the above change:&lt;br /&gt;&lt;pre&gt;[oracle@ora11gr1b fd]$ ps -fp 6364&lt;br /&gt;UID        PID  PPID  C STIME TTY          TIME CMD&lt;br /&gt;oracle    6364     1  0 19:59 ?        00:00:00 ora_mrp0_ora11gr1&lt;br /&gt;[oracle@ora11gr1b fd]$ strace -e pread,pwrite -p 6364&lt;br /&gt;Process 6364 attached - interrupt to quit&lt;br /&gt;pread(32,..., 512, 45568) = 512&lt;br /&gt;pread(32,..., 1024, 46080) = 1024&lt;br /&gt;pread(30,..., 8192, 2228224) = 8192&lt;br /&gt;pread(30,..., 8192, 259858432) = 8192&lt;br /&gt;pread(31,..., 8192, 9461760) = 8192&lt;/pre&gt;I've set &lt;span style="font-style:italic;"&gt;filesystemio_options=none&lt;/span&gt; so we can observe &lt;span style="font-style:italic;"&gt;pread/pwrite syscalls&lt;/span&gt; which are easier to follow compared to asynch &lt;span style="font-style:italic;"&gt;io_submit/io_getvents system calls&lt;/span&gt; (and we don't care about O_DIRECT flag either).&lt;br /&gt;&lt;br /&gt;Let's check what are these file descriptors:&lt;br /&gt;&lt;pre&gt;[oracle@ora11gr1b fd]$ cd /proc/6364/fd&lt;br /&gt;[oracle@ora11gr1b fd]$ file 30&lt;br /&gt;30: symbolic link to `/u01/oradata/ORA11GR1B/datafile/o1_mf_undotbs1_0fk5fp2c_.dbf'&lt;br /&gt;[oracle@ora11gr1b fd]$ file 31&lt;br /&gt;31: symbolic link to `/u01/oradata/ORA11GR1B/datafile/o1_mf_users_0ik5fp4u_.dbf'&lt;br /&gt;[oracle@ora11gr1b fd]$ file 32&lt;br /&gt;32: symbolic link to `/u01/oradata/ORA11GR1B/onlinelog/o1_mf_9_4qn2rkhk_.log'&lt;/pre&gt;In other words, the process read from standby logfile, undo and users (this is where our table is) tablespaces. However, as you might notice, all these calls were reads, we didn't write anything.&lt;br /&gt;&lt;br /&gt;From time to time MRP gets a bit more interesting, for example during logfile switches:&lt;br /&gt;&lt;pre&gt;...&lt;br /&gt;pwrite(27,..., 16384, 16384) = 16384&lt;br /&gt;pread(27,..., 16384, 16384) = 16384&lt;br /&gt;pread(28,..., 8192, 8192) = 8192&lt;br /&gt;pread(29,..., 8192, 8192) = 8192&lt;br /&gt;pread(30,..., 8192, 8192) = 8192&lt;br /&gt;pread(31,..., 8192, 8192) = 8192&lt;br /&gt;pread(27,..., 16384, 393216) = 16384&lt;br /&gt;pwrite(28,..., 8192, 8192) = 8192&lt;br /&gt;pwrite(29,..., 8192, 8192) = 8192&lt;br /&gt;pwrite(30,..., 8192, 8192) = 8192&lt;br /&gt;pwrite(31,..., 8192, 8192) = 8192&lt;br /&gt;...&lt;/pre&gt;Here we actually wrote (27 is a controlfile, 28 and 29 are system and sysaux tablespaces respectively) something. However, from the offset (fourth parameter) you can realize that we are writing to the second block in these datafiles. There is no (can't be) any user data there.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Who is writing the data then?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The first thing you might want to check is, of course, the database writer process:&lt;br /&gt;&lt;pre&gt;[oracle@ora11gr1b ~]$ ps -fp 6303&lt;br /&gt;UID        PID  PPID  C STIME TTY          TIME CMD&lt;br /&gt;oracle    6303     1  0 19:55 ?        00:00:00 ora_dbw0_ora11gr1&lt;br /&gt;[oracle@ora11gr1b ~]$ strace -e pread,pwrite -p 6303&lt;br /&gt;Process 6303 attached - interrupt to quit&lt;br /&gt;pwrite(23,..., 8192, 2097152) = 8192&lt;br /&gt;pwrite(23,..., 8192, 35987456) = 8192&lt;br /&gt;pwrite(24,..., 8192, 9461760) = 8192&lt;/pre&gt;This is the output produced by standby's dbwr right after we updated our table on the source. We wrote two undo blocks (23) and one block in users tablespace (24). By looking at the offset for file descriptor 24 we can confirm that we wrote the table itself:&lt;br /&gt;&lt;pre&gt;SQL&gt; select segment_name&lt;br /&gt; 2   from dba_extents&lt;br /&gt; 3   where tablespace_name='USERS'&lt;br /&gt; 4    and 9461760/8192 between block_id and block_id + blocks-1;&lt;br /&gt;&lt;br /&gt;SEGMENT_NAME&lt;br /&gt;--------------------------&lt;br /&gt;T&lt;/pre&gt;From the above you can confirm that it is DBWR process which wrote the changes for us and it plays crucial role during your standby database operations.&lt;br /&gt;&lt;br /&gt;MRP's workload consists mostly from &lt;span style="font-style:italic;"&gt;reading&lt;/span&gt; the redo stream, datafiles, controlfiles and occasional writes into the controlfile and datafiles header.&lt;br /&gt;&lt;br /&gt;If your standby is suffering from the redo apply performance, you may want to pay attention to both MRP &lt;span style="font-style:italic;"&gt;and&lt;/span&gt; DBWR processes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-5563627845038955843?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/11g-managed-recovery-process.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-4966029699927956151</guid><pubDate>Tue, 20 Jan 2009 11:10:00 +0000</pubDate><atom:updated>2009-01-20T21:52:16.274-05:00</atom:updated><title>Latch waits in 11G: queuing</title><description>Following up on my previous &lt;a href="http://afatkulin.blogspot.com/2009/01/longhold-latch-waits-on-linux.html"&gt;post&lt;/a&gt; about 11G latch waits using semaphore post-wait with a latch holder...&lt;br /&gt;&lt;br /&gt;What I was curious to confirm is whether latch holder posts only the first waiter in the list or all of them?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The results&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here are the results I've got using three sessions:&lt;br /&gt;&lt;pre&gt;&lt;ol&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Session 1:&lt;/span&gt; gets RC latch&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Session 2:&lt;/span&gt; misses the latch, begins to sleep&lt;br/&gt;&lt;span style="font-style:italic;"&gt;semop(753664, 0x7fbfffa348, 1)&lt;/span&gt;&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Session 3:&lt;/span&gt; misses the latch, begins to sleep&lt;br/&gt;&lt;span style="font-style:italic;"&gt;semop(753664, 0x7fbfff5f58, 1)&lt;/span&gt;&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Session 1:&lt;/span&gt; completes, posts session 2&lt;br/&gt;&lt;span style="font-style:italic;"&gt;semctl(753664, 28, SETVAL, 0x1)&lt;/span&gt;&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Session 2:&lt;/span&gt; completes, posts session 3&lt;br/&gt;&lt;span style="font-style:italic;"&gt;semctl(753664, 31, SETVAL, 0x1)&lt;/span&gt;&lt;br /&gt;&lt;/ol&gt;&lt;/pre&gt;Note that though we used the same &lt;span style="font-style:italic;"&gt;semaphore set&lt;/span&gt; (753664), sessions 1 and 2 posted different &lt;span style="font-style:italic;"&gt;semaphores&lt;/span&gt; within that set (28 and 31 respectively) and waiters were wakened up by their respective holders according to the order they begun to wait.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;sleeps + spin_gets = misses ?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's an ideal case, of course, as just awakened process might at least have to compete for the same latch with a previous holder if that holder decided to get the same latch again. If awakened process doesn't manage to get the latch during the spin, it'll have to sleep again (thus breaking the above formula). However, the &lt;span style="font-style:italic;"&gt;proximity&lt;/span&gt; to the above equation might be useful as one of the indicators whether latch is a &lt;span style="font-style:italic;"&gt;potential&lt;/span&gt; subject to the "new" algorithm or not (this new wait model should tend to produce less sleeps, hence spins, per miss, otherwise there were not much points about it).&lt;br /&gt;&lt;pre&gt;SQL&gt; select misses, sleeps, spin_gets, sleeps+spin_gets&lt;br /&gt;  2   from v$latch&lt;br /&gt;  3   where name='Result Cache: Latch';&lt;br /&gt; &lt;br /&gt;    MISSES     SLEEPS  SPIN_GETS SLEEPS+SPIN_GETS&lt;br /&gt;---------- ---------- ---------- ----------------&lt;br /&gt;      8208        275       7946             8221&lt;/pre&gt;The numbers are pretty close indeed. All this made me a bit more curios and I've decided to take a bit closer look at what happens in the strace then waiter has to sleep more than once in a row.&lt;br /&gt;&lt;br /&gt;I've ran two parallel sessions executing a loop which selects from result cache...&lt;br /&gt;&lt;pre&gt;begin&lt;br /&gt; for i in 1 .. 1000000&lt;br /&gt; loop&lt;br /&gt;  for cur in (select /*+ result_cache */ * from t where n=1)&lt;br /&gt;  loop&lt;br /&gt;   null;&lt;br /&gt;  end loop;&lt;br /&gt; end loop;&lt;br /&gt;end;&lt;/pre&gt; ...made sure the above formula became distorted further and took a look at strace c&lt;span style="font-style:italic;"&gt;&lt;/span&gt;utput for one of the sessions which led me to an interesting discovery:&lt;br /&gt;&lt;pre&gt;[oracle@ora11gr1a ~]$ strace -e semop,semctl,semtimedop -p 7591&lt;br /&gt;Process 7591 attached - interrupt to quit&lt;br /&gt;...&lt;br /&gt;semctl(1015808, 30, SETVAL, 0x1)        = 0&lt;br /&gt;semctl(1015808, 30, SETVAL, 0x1)        = 0&lt;br /&gt;semtimedop(1015808, 0x7fbfff42b8, 1, {0, 10000000}) = 0&lt;br /&gt;semtimedop(1015808, 0x7fbfff42b8, 1, {0, 10000000}) = -1 EAGAIN (Resource temporarily unavailable)&lt;br /&gt;semop(1015808, 0x7fbfff5f58, 1)         = 0&lt;br /&gt;...&lt;/pre&gt;What we see there is traditional time-based sleep using &lt;span style="font-style:italic;"&gt;semtimedop syscall&lt;/span&gt;. Does that means that Oracle still can choose between algorithms dynamically? The answer, according to extended SQL trace, seems to be &lt;span style="font-style:italic;"&gt;no&lt;/span&gt;, as I wasn't able to match these &lt;span style="font-style:italic;"&gt;semtimedop syscalls&lt;/span&gt; with latch waits in SQL trace (so these &lt;span style="font-style:italic;"&gt;syscalls&lt;/span&gt; were used for something else).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;P.S. It looks like 11G has invalidated all that pseudo code you may find around about latch acquisition algorithm Oracle uses.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-4966029699927956151?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/latch-waits-in-11g-queuing.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">1</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-2669024593286241615</guid><pubDate>Mon, 19 Jan 2009 23:05:00 +0000</pubDate><atom:updated>2009-01-19T20:53:49.259-05:00</atom:updated><title>"LONGHOLD" latch waits on Linux</title><description>Take a look at the figure from my previous blog post:&lt;br /&gt;&lt;pre&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch     5001      0.53      53.71          0          0          0      500000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total     5003      0.53      53.71          0          0          0      500000&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt;Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  latch free                                      1       53.20         53.20&lt;br /&gt;&lt;/pre&gt;What's unusual about it is a single latch wait for 53 seconds without consuming any CPU time. This is something different from a usual&lt;br /&gt;&lt;pre&gt;get -&gt; miss -&gt; spin -&gt; sleep&lt;/pre&gt;latch acquisition algorithm we generally used to.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Sequence of events&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To remind you, the sequence of events goes like this:&lt;ol&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;First session:&lt;/span&gt; do &lt;span style="font-style:italic;"&gt;select /*+ result_cache */ * from t&lt;/span&gt; which places a single big result into Result Cache memory.&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;First session:&lt;/span&gt; do &lt;span style="font-style:italic;"&gt;select count(*) from v$result_cache_memory&lt;/span&gt; which grabs Result Cache latch for a long amount of time.&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Second session:&lt;/span&gt; do &lt;span style="font-style:italic;"&gt;select /*+ result_cache */ * from t&lt;/span&gt; which forces us to wait until Result Cache latch will be freed up by our first session.&lt;/ol&gt;&lt;span style="font-weight:bold;"&gt;Latch statistics&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's take a look at v$latch right after we execute the above three steps:&lt;br /&gt;&lt;pre&gt;SQL&gt; select gets, misses, sleeps&lt;br /&gt;  2   from v$latch&lt;br /&gt;  3   where name='Result Cache: Latch';&lt;br /&gt; &lt;br /&gt;      GETS     MISSES     SLEEPS&lt;br /&gt;---------- ---------- ----------&lt;br /&gt;     53494          1          1&lt;/pre&gt;As we can see, the session indeed went into a sleep after a single miss. The first thing which may come into your mind is that I'm running this on a single CPU box where we don't spin if we miss a latch (due to &lt;span style="font-style:italic;"&gt;_spin_count=1&lt;/span&gt; being default on a single CPU boxes). This is not the case:&lt;br /&gt;&lt;pre&gt;SQL&gt; show parameter cpu_count&lt;br /&gt;&lt;br /&gt;NAME                                 TYPE        VALUE&lt;br /&gt;------------------------------------ ----------- ------------------------------&lt;br /&gt;cpu_count                            integer     2&lt;/pre&gt;And my DB's &lt;span style="font-style:italic;"&gt;_spin_count&lt;/span&gt; has a default value of 2000 as well.&lt;br /&gt;&lt;br /&gt;Let's look a bit further:&lt;pre&gt;SQL&gt; select location, sleep_count, wtr_slp_count, longhold_count&lt;br /&gt;  2   from v$latch_misses&lt;br /&gt;  3   where parent_name='Result Cache: Latch'&lt;br /&gt;  4    and sleep_count+wtr_slp_count+longhold_count &gt; 0;&lt;br /&gt; &lt;br /&gt;LOCATION                       SLEEP_COUNT WTR_SLP_COUNT LONGHOLD_COUNT&lt;br /&gt;------------------------------ ----------- ------------- --------------&lt;br /&gt;Result Cache: Serialization12            0             1              0&lt;br /&gt;Result Cache: Serialization16            1             0              1&lt;/pre&gt;Note &lt;span style="font-style:italic;"&gt;LONGHOLD_COUN&lt;/span&gt;T column equals 1 for &lt;span style="font-style:italic;"&gt;Result Cache: Serialization16&lt;/span&gt;. That means that we've holded a latch for the entire duration of someone else's sleep which, again, conforms to our observation. But what is the underlying mechanism for this?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Under the hood&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is an excerpt from our second session's strace output:&lt;br /&gt;&lt;pre&gt;...&lt;br /&gt;getrusage(RUSAGE_SELF, {ru_utime={0, 728889}, ru_stime={1, 317799}, ...}) = 0&lt;br /&gt;getrusage(RUSAGE_SELF, {ru_utime={0, 728889}, ru_stime={1, 317799}, ...}) = 0&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;semop(98304, 0x7fbfff5f58, 1)           = 0 -- enters the sleep here&lt;/span&gt;&lt;br /&gt;times({tms_utime=72, tms_stime=131, tms_cutime=0, tms_cstime=0}) = 429496789&lt;br /&gt;getrusage(RUSAGE_SELF, {ru_utime={0, 729889}, ru_stime={1, 317799}, ...}) = 0&lt;br /&gt;...&lt;/pre&gt;And the first session:&lt;br /&gt;&lt;pre&gt;...&lt;br /&gt;mmap(0x2a972e7000, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 9, 0) = 0x2a972e7000&lt;br /&gt;mmap(0x2a972f7000, 65536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 9, 0) = 0x2a972f7000&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;semctl(98304, 31, SETVAL, 0x7f00000001) = 0 -- issues upon completion&lt;/span&gt;&lt;br /&gt;getrusage(RUSAGE_SELF, {ru_utime={206, 80670}, ru_stime={2, 686591}, ...}) = 0&lt;br /&gt;getrusage(RUSAGE_SELF, {ru_utime={206, 81670}, ru_stime={2, 686591}, ...}) = 0&lt;br /&gt;...&lt;/pre&gt;In other words:&lt;br /&gt;&lt;ol&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;Second session:&lt;/span&gt; upon discovering that the latch is busy, it begins to sleep by issuing &lt;br/&gt;&lt;span style="font-style:italic;"&gt;semop(98304, 0x7fbfff5f58, 1)&lt;/span&gt; command.&lt;br /&gt;&lt;li/&gt;&lt;span style="font-weight:bold;"&gt;First session:&lt;/span&gt; upon completion issues &lt;br/&gt;&lt;span style="font-style:italic;"&gt;semctl(98304, 31, SETVAL, 0x7f00000001)&lt;/span&gt; which wakes our second session up.&lt;/ol&gt;The first argument is a &lt;span style="font-style:italic;"&gt;semid&lt;/span&gt; which means that both sessions are operating on the same semaphore. Both semaphore &lt;span style="font-style:italic;"&gt;syscalls&lt;/span&gt; are not presented if we execute each of the above sessions separately.&lt;br /&gt;&lt;br /&gt;Though we do sleep under normal latch acquisition algorithm as well, the sleep is based on &lt;span style="font-style:italic;"&gt;time&lt;/span&gt;, not the latch holder posting us.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Which algorithm?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The interesting question to answer is how the waiter determines which algorithm to use. It could be (A) a special "longhold" mode get for a latch or (B) the waiter might check for how long the latch was already held. Thus far I tend to think that it is more close to (A) (a special get mode or a separate flag somewhere) because if we execute above two sessions concurrently we always get an enormously unusual number of sleeps even if we place only one single-row result into Result Cache memory (thus making sure there are no significant delays selecting from &lt;span style="font-style:italic;"&gt;v$result_cache_memory&lt;/span&gt;).&lt;br /&gt;&lt;br /&gt;Maybe some of you could share other ideas as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-2669024593286241615?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/longhold-latch-waits-on-linux.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-132606393814285152</guid><pubDate>Sun, 18 Jan 2009 11:14:00 +0000</pubDate><atom:updated>2009-01-18T21:57:57.206-05:00</atom:updated><title>v$result_cache_memory or what is the newest method to hang stuff up?</title><description>Sometimes I get quite amused by the "side effects" you can discover while playing with features.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Prerequisites&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First, we need to place something relatively big into result cache memory. The easiest way to archive this is start like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; alter system set result_cache_max_size=64m;&lt;br /&gt;&lt;br /&gt;System altered.&lt;br /&gt;&lt;br /&gt;SQL&gt; alter system set result_cache_max_result=100;&lt;br /&gt;&lt;br /&gt;System altered.&lt;/pre&gt;The first parameter controls how much memory out of your shared pool Oracle can dedicate for result cache. The second parameter tells how much memory, in percent, can be dedicated for a single resultset. Default value is 5 and I've set it to 100 so we don't have to dedicate a lot of memory to demonstrate my point.&lt;br /&gt;&lt;br /&gt;All we have to do now is populate the result cache memory:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table t as&lt;br /&gt;  2   select level n, rpad('*', 100, '*') v&lt;br /&gt;  3    from dual&lt;br /&gt;  4    connect by level &lt;= 500000;&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt; &lt;br /&gt;SQL&gt; begin&lt;br /&gt;  2   for cur in (select /*+ result_cache*/ * from t)&lt;br /&gt;  3   loop&lt;br /&gt;  4    null;&lt;br /&gt;  5   end loop;&lt;br /&gt;  6  end;&lt;br /&gt;  7  /&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;&lt;span style="font-weight:bold;"&gt;What's next?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's check how much result cache memory is allocated:&lt;br /&gt;&lt;pre&gt;SQL&gt; set timing on&lt;br /&gt;SQL&gt; select count(*) from v$result_cache_memory;&lt;br /&gt; &lt;br /&gt;  COUNT(*)&lt;br /&gt;----------&lt;br /&gt;     65536&lt;br /&gt; &lt;br /&gt;Executed in 55.984 seconds&lt;/pre&gt;That's almost a minute to get the results, yes.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;So what?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are (nasty) consequences.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;It's time to buy Core i7&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's start by taking a look at the trace file of our session:&lt;br /&gt;&lt;pre&gt;SQL ID: 0zudkuq64dkwx&lt;br /&gt;Plan Hash: 600544352&lt;br /&gt;select count(*)&lt;br /&gt;from&lt;br /&gt; v$result_cache_memory&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        1     54.65      56.96          0          0          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        3     54.65      56.96          0          0          0           1&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 38&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  SORT AGGREGATE (cr=0 pr=0 pw=0 time=0 us)&lt;br /&gt;  65536   FIXED TABLE FULL X$QESRCMEM (cr=0 pr=0 pw=0 time=0 us cost=0 size=13 card=1)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  SQL*Net message from client                     2        0.00          0.01&lt;/pre&gt;First of all, all the time was spent actively consuming CPU. Selecting from X$QESRCMEM became a pretty expensive thing (in fact, at first I confused such a huge delay with a session being simply hang while spinning on the CPU).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Can't use result cache&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Any session which attempts to use result cache will be forced to wait until our first session completes:&lt;br /&gt;&lt;pre&gt;SQL ID: bzf4nvdav1m2j&lt;br /&gt;Plan Hash: 1601196873&lt;br /&gt;SELECT /*+ result_cache*/ *&lt;br /&gt;FROM&lt;br /&gt; T&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch     5001      0.53      53.71          0          0          0      500000&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total     5003      0.53      53.71          0          0          0      500000&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 38     (recursive depth: 1)&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt; 500000  RESULT CACHE  49yjv3h37cjhz39gkd90p4413p (cr=0 pr=0 pw=0 time=0 us)&lt;br /&gt;      0   TABLE ACCESS FULL T (cr=0 pr=0 pw=0 time=0 us cost=2127 size=30436770 card=468258)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  latch free                                      1       53.20         53.20&lt;br /&gt;&lt;/pre&gt;And the latch name is...&lt;br /&gt;&lt;pre&gt;WAIT #2: nam='latch free' ela= 53205337 address=1610838648 number=377 tries=0 obj#=-1 tim=1232316789354155&lt;br /&gt;&lt;br /&gt;SQL&gt; select name from v$latch where latch#=377;&lt;br /&gt; &lt;br /&gt;NAME&lt;br /&gt;----------------------------------------------------------------&lt;br /&gt;Result Cache: Latch&lt;/pre&gt;Surprised? I'm not. Don't expect me to start complaining about the absence of shared mode gets again.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The Real Deal or result_cache_mode = force&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Things gets really nasty if you set &lt;span style="font-style:italic;"&gt;result_cache_mode = force&lt;/span&gt; which has a potential outcome of rendering your entire system unusable for some extended period of time. Because even the simplest queries like &lt;span style="font-style:italic;"&gt;select * from dual&lt;/span&gt; will hang, escalating situation to a point where your normal users will be unable to even connect to a database, let alone executing the queries. For example, trying to connect with SQL*Plus results in the following:&lt;br /&gt;&lt;pre&gt;SQL ID: d6vwqbw6r2ffk&lt;br /&gt;Plan Hash: 1388734953&lt;br /&gt;SELECT USER&lt;br /&gt;FROM&lt;br /&gt; DUAL&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.01       0.01          0          0          0           0&lt;br /&gt;Execute      1      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        2      0.00      43.40          0          0          0           1&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        4      0.01      43.41          0          0          0           1&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 38&lt;br /&gt;&lt;br /&gt;Rows     Row Source Operation&lt;br /&gt;-------  ---------------------------------------------------&lt;br /&gt;      1  RESULT CACHE  4c48ztv5ztq25dkd78360h2uxb (cr=0 pr=0 pw=0 time=0 us)&lt;br /&gt;      1   FAST DUAL  (cr=0 pr=0 pw=0 time=0 us cost=2 size=0 card=1)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  SQL*Net message to client                       2        0.00          0.00&lt;br /&gt;  latch free                                      1       43.39         43.39&lt;br /&gt;  SQL*Net message from client                     2        0.00          0.00&lt;br /&gt;&lt;/pre&gt;I don't have to mention the latch name again.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Real World?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What are the chances of meeting the above situation on some real system? First of all, having a system with more than a 100GB SGA is not uncommon these day. The thing gets worse the more memory is being occupied by a &lt;span style="font-style:italic;"&gt;single&lt;/span&gt; result. Having a lot of small results seems to be not a problem. If we take 5% as a default threshold to be eligible to be placed into result cache memory, facing the above situation will require 1280M for result cache memory. That looks like quite a big number, however, on the scale of hundred gigs SGA it's no longer seems to be improbable. Aggregating a multi gigabytes of data into 64MB result doesn't sound insane either (at least not to me, you may have a different opinion here).&lt;br /&gt;&lt;br /&gt;Plus it takes someone to query v$result_cache_memory. So just don't do it. There are other views (like v$result_cache_objects) which you can use to at least get a part of that info, alas at lower granularity.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Environment&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The above tests were verified on Oracle 11GR1 running on Windows XP x64 (native) and Linux x86-64 under VMware (with XP x64 acting as a host OS).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-132606393814285152?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/vresultcachememory-or-what-is-newest.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-6434456866649130453</guid><pubDate>Fri, 16 Jan 2009 23:20:00 +0000</pubDate><atom:updated>2009-01-16T19:11:52.369-05:00</atom:updated><title>insert /*+ append */ into ... values (...)</title><description>Alex Egorov has left a very fascinating comment to my post about &lt;a href="http://afatkulin.blogspot.com/2009/01/11g-adaptive-direct-path-reads-what-is.html"&gt;11G adaptive direct path reads&lt;/a&gt; which I thought definitely deserves mentioning.&lt;br /&gt;&lt;br /&gt;What we are talking about here is a serious behavior change how statements like...&lt;br /&gt;&lt;pre&gt;insert /*+ append */ into t values (1)&lt;/pre&gt;...are handled in 11G.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Saving yourself from yourself... not anymore&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;In previous Oracle versions, the &lt;span style="font-style:italic;"&gt;append&lt;/span&gt; hint in the cases like above was simply ignored for some good reasons. The hint is being obeyed in 11G.&lt;br /&gt;&lt;br /&gt;To better elaborate what that means, let's take the following example:&lt;br /&gt;&lt;pre&gt;begin&lt;br /&gt; for i in 1 .. 1000&lt;br /&gt; loop&lt;br /&gt;  insert /*+ append */ into t values (i);&lt;br /&gt;  commit;&lt;br /&gt; end loop;&lt;br /&gt;end;&lt;/pre&gt;...and compare how behavior will change in 11G.&lt;br /&gt;&lt;br /&gt;First of all, I've seen legacy code like the above in many places where developers were putting append hint everywhere they could because they heard it is like &lt;span style="font-style:italic;"&gt;fast=true&lt;/span&gt; thing.&lt;br /&gt;&lt;br /&gt;After you upgrade, you might expect the following:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li/&gt;If you don't do commit after each row -- the code will break itself with &lt;span style="font-style:italic;"&gt;ORA-12838: cannot read/modify an object after modifying it in parallel&lt;/span&gt;.&lt;br /&gt;&lt;li/&gt;If you do commit (as in my example) -- you have all chances to grow the table beyond the skies as each direct path insert writes beyond the HWM (in 11G, the above code will grow the table by 1000 blocks each time it is executed).&lt;br /&gt;&lt;li/&gt;Serious locking issues as each direct path insert has to obtain an exclusive lock on the table first.&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;The above might produce quite an unpleasant surprises for some of you...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-6434456866649130453?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/insert-append-into-values.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">10</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-6654449729965596193</guid><pubDate>Thu, 15 Jan 2009 21:27:00 +0000</pubDate><atom:updated>2009-01-27T11:43:17.288-05:00</atom:updated><title>Hot backup mode and a little known fact</title><description>Most people are aware of the implication hot backup puts on your redo stream. They'll tell you that on first modification Oracle will dump the entire block image into a redo stream and subsequent writes of the block will go as usual.&lt;br /&gt;&lt;br /&gt;This is only half of the story.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's start by creating a simple table I'll use as an example:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table t pctfree 99 pctused 1 as&lt;br /&gt;  2     select level n&lt;br /&gt;  3             from dual&lt;br /&gt;  4             connect by level &lt;= 100;&lt;br /&gt;&lt;br /&gt;Table created.&lt;/pre&gt;Now, let's measure the amount of redo generated if I'm going to update all rows in the table:&lt;br /&gt;&lt;pre&gt;SQL&gt; set autot traceonly stat&lt;br /&gt;SQL&gt; update t set n=n;&lt;br /&gt;&lt;br /&gt;100 rows updated.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         30  recursive calls&lt;br /&gt;         20  db block gets&lt;br /&gt;         44  consistent gets&lt;br /&gt;         18  physical reads&lt;br /&gt;      12152  redo size&lt;br /&gt;        826  bytes sent via SQL*Net to client&lt;br /&gt;        769  bytes received via SQL*Net from client&lt;br /&gt;          3  SQL*Net roundtrips to/from client&lt;br /&gt;          2  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;        100  rows processed&lt;/pre&gt;That's almost 12K of redo. Let's see what happens if I put tablespace into a hot backup mode:&lt;br /&gt;&lt;pre&gt;SQL&gt; alter tablespace users begin backup;&lt;br /&gt;&lt;br /&gt;Tablespace altered.&lt;br /&gt;&lt;br /&gt;SQL&gt; update t set n=n;&lt;br /&gt;&lt;br /&gt;100 rows updated.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;         20  db block gets&lt;br /&gt;         20  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;     152708  redo size&lt;br /&gt;        830  bytes sent via SQL*Net to client&lt;br /&gt;        769  bytes received via SQL*Net from client&lt;br /&gt;          3  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;        100  rows processed&lt;/pre&gt;The amount of redo grew up to almost 150K. Oracle has to dump the entire block image into a redo stream due to fractured blocks problem. If we do the same update second time:&lt;br /&gt;&lt;pre&gt;SQL&gt; update t set n=n;&lt;br /&gt;&lt;br /&gt;100 rows updated.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;         17  db block gets&lt;br /&gt;         20  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;      11980  redo size&lt;br /&gt;        830  bytes sent via SQL*Net to client&lt;br /&gt;        769  bytes received via SQL*Net from client&lt;br /&gt;          3  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;        100  rows processed&lt;/pre&gt;It goes as usual. In fact, and this is kind of amazing, this is what most of the sources available on the internet will tell you about the behavior: the full image of the block gets written only on first modification in order to prevent fractured blocks problem and subsequent modifications will go as usuals.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Only half of the story&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let me continue with my example:&lt;br /&gt;&lt;pre&gt;SQL&gt; alter system flush buffer_cache;&lt;br /&gt;&lt;br /&gt;System altered.&lt;br /&gt;&lt;br /&gt;SQL&gt; update t set n=n;&lt;br /&gt;&lt;br /&gt;100 rows updated.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          0  recursive calls&lt;br /&gt;         20  db block gets&lt;br /&gt;         20  consistent gets&lt;br /&gt;         20  physical reads&lt;br /&gt;     151916  redo size&lt;br /&gt;        831  bytes sent via SQL*Net to client&lt;br /&gt;        769  bytes received via SQL*Net from client&lt;br /&gt;          3  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;        100  rows processed&lt;/pre&gt;What you see here is the entire image of the blocks written into a redo stream again.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The true story&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Corrected 27-Jan-2009&lt;/span&gt;: initially I thought that it is a write to disk what matters, however, thanks to Jonathan Lewis for correcting me -- it's a read from disk to buffer. Please see his first comment.&lt;br /&gt;&lt;br /&gt;Don't confuse correlation with causation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-6654449729965596193?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/hot-backup-mode-and-little-known-fact.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-3390151551894897393</guid><pubDate>Thu, 15 Jan 2009 02:44:00 +0000</pubDate><atom:updated>2009-01-16T21:31:20.341-05:00</atom:updated><title>Consistent gets from cache (fastpath)</title><description>There is an intresting optimization introduced in 11G regarding the way blocks are accessed in the buffer cache.&lt;br /&gt;&lt;br /&gt;Let's start with a simple example to demonstrate my point.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;10GR2 example&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;SQL&gt; create table t (&lt;br /&gt; 2   n number,&lt;br /&gt; 3   v varchar2(100),&lt;br /&gt; 4   constraint pk_n primary key (n)&lt;br /&gt; 5  );&lt;br /&gt;&lt;br /&gt;Table created&lt;br /&gt;&lt;br /&gt;SQL&gt;&lt;br /&gt;SQL&gt; insert into t&lt;br /&gt; 2   select level, rpad('*', 100, '*')&lt;br /&gt; 3    from dual&lt;br /&gt; 4    connect by level &lt;= 1000;&lt;br /&gt;&lt;br /&gt;1000 rows inserted&lt;br /&gt;&lt;br /&gt;SQL&gt; commit;&lt;br /&gt;&lt;br /&gt;Commit complete&lt;/pre&gt;I'm going to create the procedure to return number of consistent gets in my session:&lt;br /&gt;&lt;pre&gt;SQL&gt; create or replace procedure get_cg(&lt;br /&gt; 2   p_cg out number&lt;br /&gt; 3  ) is&lt;br /&gt; 4  begin&lt;br /&gt; 5   select ms.value end into p_cg&lt;br /&gt; 6    from v$mystat ms, v$statname sn&lt;br /&gt; 7    where ms.STATISTIC#=sn.STATISTIC#&lt;br /&gt; 8     and sn.NAME='consistent gets';&lt;br /&gt; 9  end get_cg;&lt;br /&gt;10  /&lt;br /&gt;&lt;br /&gt;Procedure created&lt;/pre&gt;Let's execute a simple select and measure the number of consistent gets:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cg_a  number;&lt;br /&gt; 4  begin&lt;br /&gt; 5   get_cg(l_cg_b);&lt;br /&gt; 6   for cur in (select n from (select mod(level, 1000)+1 l from dual connect by&lt;br /&gt; level &lt;= 100000) l, t where t.n=l.l)&lt;br /&gt; 7   loop&lt;br /&gt; 8    null;   &lt;br /&gt; 9   end loop;&lt;br /&gt; 10   get_cg(l_cg_a);&lt;br /&gt; 11   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 12  end;&lt;br /&gt; 13  /&lt;br /&gt;&lt;br /&gt;consistent gets: 100012&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;This is somewhat expected number, we did an index lookup (a unique scan) for all 100K rows returned from our connect by query.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;11GR1 example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's do the same example in 11G. I've slightly modified the procedure to return two statistics -- &lt;span style="font-style:italic;"&gt;consistent gets&lt;/span&gt; and &lt;span style="font-style:italic;"&gt;consistent gets from cache (fastpath)&lt;/span&gt;:&lt;br /&gt;&lt;pre&gt;SQL&gt; create or replace procedure get_cg(&lt;br /&gt; 2   p_cg out number,&lt;br /&gt; 3   p_cgfp out number&lt;br /&gt; 4  ) is&lt;br /&gt; 5  begin&lt;br /&gt; 6   select max(case sn.NAME when 'consistent gets' then ms.value end),&lt;br /&gt; 7     max(case sn.NAME when 'consistent gets from cache (fastpath)'&lt;br /&gt; then ms.value end)&lt;br /&gt; 8     into p_cg, p_cgfp&lt;br /&gt; 9    from v$mystat ms, v$statname sn&lt;br /&gt;10    where ms.STATISTIC#=sn.STATISTIC#&lt;br /&gt;11     and sn.NAME in ('consistent gets', 'consistent gets from &lt;br /&gt;cache (fastpath)');&lt;br /&gt;12  end get_cg;&lt;br /&gt;13  /&lt;br /&gt;&lt;br /&gt;Procedure created&lt;br /&gt;&lt;br /&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select n from (select mod(level, 1000)+1 l from dual connect by &lt;br /&gt;level &lt;= 100000) l, t where t.n=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16 &lt;br /&gt;/&lt;br /&gt;consistent gets: 2602&lt;br /&gt;consistent gets (fastpath): 1400&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;That's quite a difference! 2602 consistent gets for 11GR1 versus ~100K for 10GR2. Note a new &lt;span style="font-style:italic;"&gt;consistent gets (fastpath)&lt;/span&gt; statistic appearing in this test case.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;A slightly different example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;To make you a bit more curious, here is a slightly different example executed on 11GR1:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select n from (select trunc(dbms_random.value(1, 1000)) l from &lt;br /&gt;dual connect by level &lt;= 100000) l, t where t.n=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;consistent gets: 101998&lt;br /&gt;consistent gets (fastpath): 2934&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;As soon as we've started reading index blocks in a random fashion, the effect, well, disappeared. Thus far optimization seems to be kicking in only if we repeaditly accessing the same block over and over again.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Another slightly different example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;10GR2:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cg_a  number;&lt;br /&gt; 4  begin&lt;br /&gt; 5   get_cg(l_cg_b);&lt;br /&gt; 6   for cur in (select v from (select mod(level, 1000)+1 l from dual connect by &lt;br /&gt;level &lt;= 100000) l, t where t.n=l.l)&lt;br /&gt; 7   loop&lt;br /&gt; 8    null;&lt;br /&gt; 9   end loop;&lt;br /&gt; 10   get_cg(l_cg_a);&lt;br /&gt; 11   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 12  end;&lt;br /&gt; 13  /&lt;br /&gt;consistent gets: 201001&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;What we are doing here is reading the data from a table itself (not only from index). Again, that's expected number. We had to do an &lt;span style="font-style:italic;"&gt;index unique scan&lt;/span&gt; followed by &lt;span style="font-style:italic;"&gt;table access by rowid&lt;/span&gt;, resulting in two LIOs per returned row. Now, take a look what happens in 11GR1:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select v from (select mod(level, 1000)+1 l from dual connect by &lt;br /&gt;level &lt;= 100000) l, t where t.n=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-&lt;br /&gt;l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;consistent gets: 102602&lt;br /&gt;consistent gets (fastpath): 1400&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;In other words, index blocks were "neutralized", but table blocks were not. Another point is that optimization still works for index blocks even though we accessing table blocks in between.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Works only for index blocks?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;That's really simple to verify using the following example:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select v from (select case mod(level, 2) when 0 then &lt;br /&gt;'AAADq1AAEAAAAJHAAA' else 'AAADq1AAEAAAAJHAAB' end l from dual connect by level &lt;= &lt;br /&gt;100000) l, t where t.rowid=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-&lt;br /&gt;l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;consistent gets: 1000&lt;br /&gt;consistent gets (fastpath): 1000&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;The same example in 10GR2, of course, produces ~100K LIOs, so the optimization seems to be kicking in for table blocks as well. Further, the behavior in the previous example can not be explained by table blocks being randomly distributed since the clustering factor is perfect:&lt;br /&gt;&lt;pre&gt;SQL&gt; select sum(bn_diff)&lt;br /&gt; 2   from (&lt;br /&gt; 3    select case when bn!=lag(bn, 1, bn) over (order by n)then 1 else 0 &lt;br /&gt;end bn_diff&lt;br /&gt; 4     from (select n, dbms_rowid.rowid_block_number(rowid) bn from &lt;br /&gt;t)&lt;br /&gt; 5   );&lt;br /&gt;&lt;br /&gt;SUM(BN_DIFF)&lt;br /&gt;------------&lt;br /&gt;         15&lt;br /&gt;&lt;br /&gt;SQL&gt; select count (distinct dbms_rowid.rowid_block_number(rowid)) from t;&lt;br /&gt;&lt;br /&gt;COUNT(DISTINCTDBMS_ROWID.ROWID&lt;br /&gt;------------------------------&lt;br /&gt;                           16&lt;/pre&gt;Perhaps, there is some overhead associated with &lt;span style="font-style:italic;"&gt;fastpath&lt;/span&gt; and the logic assumes a bad clustering factor by default (which works for majority of cases) thus doesn't tries to do a fastpath for table blocks in our previous example.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Moving further&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Take a look at this:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select t1.n n1, t2.n n2 from (select mod(level, 1000)+1 l from &lt;br /&gt;dual connect by level &lt;= 100000) l, t t1, t t2 where t1.n=l.l and t2.n=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-&lt;br /&gt;l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;consistent gets: 5204&lt;br /&gt;consistent gets (fastpath): 2800&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;I've simply added the same table into a join twice. Though we were accessing exactly the same blocks from the same table, the optimization didn't span both tables.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Doesn't work across fetches&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt; 2   l_cg_b  number;&lt;br /&gt; 3   l_cgfp_b number;&lt;br /&gt; 4   l_cg_a  number;&lt;br /&gt; 5   l_cgfp_a number;&lt;br /&gt; 6  begin&lt;br /&gt; 7   get_cg(l_cg_b, l_cgfp_b);&lt;br /&gt; 8   for cur in (select n from (select 1 l from dual connect by level &lt;= 100000) &lt;br /&gt;l, t where t.n=l.l)&lt;br /&gt; 9   loop&lt;br /&gt; 10    null;&lt;br /&gt; 11   end loop;&lt;br /&gt; 12   get_cg(l_cg_a, l_cgfp_a);&lt;br /&gt; 13   dbms_output.put_line('consistent gets: '||to_char(l_cg_a-l_cg_b));&lt;br /&gt; 14   dbms_output.put_line('consistent gets (fastpath): '||to_char(l_cgfp_a-&lt;br /&gt;l_cgfp_b));&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt;consistent gets: 1008&lt;br /&gt;consistent gets (fastpath): 1001&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;While we were accessing the same row (hence, the block), we got 1K LIOs. It is explained by 11G doing implicit array fetches by 100 rows at a time and also points out at the fact that &lt;span style="font-style:italic;"&gt;consistent gets (fastpath)&lt;/span&gt; can not operate across fetches (though the same rule applies for "regular" consistent gets as well).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;In a nutshell&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Overall, this looks like a nice feature. The downside, of course, is less predictability. 11G seems to have introduces these little tricks and features all over the place, which can really help you under certain circumstances but can also make your head spin while trying to figure out for "magical" performance differences...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style:italic;"&gt;Added 16-Jan-2009: &lt;/span&gt;Take a look at &lt;a href="http://jonathanlewis.wordpress.com/2009/01/16/concurrency/"&gt;Concurrency&lt;/a&gt; post by Jonathan Lewis for some more details.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-3390151551894897393?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/consistent-gets-from-cache-fastpath.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">3</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8129309559253737069</guid><pubDate>Mon, 12 Jan 2009 06:21:00 +0000</pubDate><atom:updated>2009-01-12T02:12:27.103-05:00</atom:updated><title>Airlines...</title><description>Reliability considered to be a table stake in the airlines industry. Something you have to have if you want to play the planes game at all.&lt;br /&gt;&lt;br /&gt;I had a flight to Toronto a couple of days ago which I can describe as very fascinating and enlightening.&lt;br /&gt;&lt;br /&gt;My flight was scheduled at 8AM and I arrived to Ottawa International airport around 7:10AM. After checking in and passing required security checks I was waiting to board my plane. Almost immediately after I begun to wait, airport intercom said:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- If there is anyone willing to board an earlier flight to Toronto, you can do so.&lt;/span&gt;&lt;/blockquote&gt;Well, fine. It is better to be in Pearson earlier than waiting in Ottawa. So I boarded a flight which was supposed to take off 25 minutes earlier.&lt;br /&gt;&lt;br /&gt;As soon as everyone has boarded, they closed the doors and started to move the plane. When, suddenly, &lt;span style="font-style:italic;"&gt;boom!&lt;/span&gt; I'm observing &lt;span style="font-style:italic;"&gt;a lot&lt;/span&gt; of smoke coming from a back of the plane. The plane stops very hard, almost instantly. I can smell partially burned jet fuel. A minute later a fire extinguisher machine comes and stops a few meters from us doing nothing. I'm watching people becoming more and more curious (is the plane on fire? is it time to jump off the plane to avoid being cooked?). The pilot says:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- It seems that we've got some electronics snag here, let me try to reboot the on-board computer to see if that fixes the problem.&lt;/span&gt;&lt;/blockquote&gt;Now, that's cool. Ummm... Windows embedded or what? A couple of minutes later pilot comes again:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- It looks like our problem still persist, so I'm going to power off the entire plane and then put the power back on, let's hope it will fix our problem, &lt;span style="font-weight:bold;"&gt;because usually it does&lt;/span&gt;.&lt;/span&gt;&lt;/blockquote&gt;If there is a moment when you start thinking whether you should be sitting on the plane at all, it seems to be it. Some people has already stood up and clearly wandering how they can get off the plane. This is really a time when stewardess can help everyone:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- We were trying to figure out if there is a possibility to board the other plane but, unfortunately, all morning flights are extremely busy.&lt;/span&gt;&lt;/blockquote&gt;What a perfect argument to keep the plane's doors closed. The plane backs to life as pilots recycled the power...&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- It looks like our problem was solved, so we are going to proceed for takeoff...&lt;/span&gt;&lt;/blockquote&gt;Why do I have that strange feeling that it would better be not solved so we can board off? Anyway, they are starting to move us again... When, suddenly, &lt;span style="font-style:italic;"&gt;boom!&lt;/span&gt; I'm observing a lot of smoke coming from a back of the plane. Here we go again, a fire extinguisher machine comes. That's enough, who can show me the exit? The pilot:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- It appears that our problem reoccurred again, the brakes applied without anything telling them to apply.&lt;/span&gt;&lt;/blockquote&gt;Let me think what would happen had the brakes applied on a runway itself... not a rocket since.&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- We are going to call the maintenance to see if they can fix the problem, it should take around 25-30 minutes...&lt;/span&gt;&lt;/blockquote&gt;Right, while they are calling the maintenance, I'm watching my flight taking off... Some guy comes in and disappears under the plane.&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;- It looks like maintenance fixed the problem and after they'll be done with a paperwork, we can proceed for takeoff...&lt;/span&gt;&lt;/blockquote&gt;Read &lt;a href="http://www.amazon.com/Worst-First-Continentals-Remarkable-Comeback/dp/0471356522"&gt;the book&lt;/a&gt;, as Gordon says, if you don't do scheduled maintenance then the plane will schedule one for you. Trying to save costs? I have no doubts you can make service to be so cheap that no one wants to use it. Well, at least they gonna miss one of their KPIs, arrival on time, so there is a hope someone is going to do something about it...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8129309559253737069?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/airlines.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-3151972004631731731</guid><pubDate>Sun, 11 Jan 2009 04:45:00 +0000</pubDate><atom:updated>2009-01-11T01:03:33.331-05:00</atom:updated><title>Using DBMS_RULE_ADM to evaluate dynamic predicates</title><description>The best comments are the ones which inspire you to try and learn something new. I received one of these to my recent blog post about &lt;a href="http://afatkulin.blogspot.com/2009/01/scaling-dynamic-sql.html"&gt;Scaling dynamic SQL&lt;/a&gt;. Stas, one of my good friends, asked whether I have tried &lt;span style="font-style:italic;"&gt;DBMS_RULE_ADM&lt;/span&gt; package for this task. Well, now I did. Stas, you should be careful what you ask next time, you might get it :)&lt;br /&gt;&lt;br /&gt;The first thing we need to do is create an evaluation context:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2   l_vtl sys.re$variable_type_list;&lt;br /&gt;  3  begin&lt;br /&gt;  4   l_vtl:=sys.re$variable_type_list(&lt;br /&gt;  5    sys.re$variable_type('age', 'number', null, null),&lt;br /&gt;  6    sys.re$variable_type('balance', 'number', null, null),&lt;br /&gt;  7    sys.re$variable_type('birthdate', 'date', null, null),&lt;br /&gt;  8    sys.re$variable_type('today', 'date', null, null)&lt;br /&gt;  9   );&lt;br /&gt; 10  &lt;br /&gt; 11   dbms_rule_adm.create_evaluation_context(&lt;br /&gt; 12    evaluation_context_name =&gt; 'adverts_ctx',&lt;br /&gt; 13    variable_types =&gt; l_vtl&lt;br /&gt; 14   );&lt;br /&gt; 15  end;&lt;br /&gt; 16  /&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;We can create the rules itself now:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2   l_rules dbms_sql.Varchar2_Table;&lt;br /&gt;  3  begin&lt;br /&gt;  4   dbms_rule_adm.create_rule_set(&lt;br /&gt;  5    rule_set_name =&gt; 'adverts'&lt;br /&gt;  6   );&lt;br /&gt;  7  &lt;br /&gt;  8   l_rules(1):=':age between 16 and 18';&lt;br /&gt;  9   l_rules(2):=':birthdate = :today or :balance &gt; 1000';&lt;br /&gt; 10   l_rules(3):=':balance between 100 and 200 and :age &gt; 18';&lt;br /&gt; 11  &lt;br /&gt; 12   for i in 1 .. 3&lt;br /&gt; 13   loop&lt;br /&gt; 14    dbms_rule_adm.create_rule(&lt;br /&gt; 15     rule_name =&gt; 'rule_'||to_char(i),&lt;br /&gt; 16     condition =&gt; l_rules(i),&lt;br /&gt; 17     evaluation_context =&gt; 'adverts_ctx'&lt;br /&gt; 18    );&lt;br /&gt; 19  &lt;br /&gt; 20    dbms_rule_adm.add_rule(&lt;br /&gt; 21     rule_name =&gt; 'rule_'||to_char(i),&lt;br /&gt; 22     rule_set_name =&gt; 'adverts'&lt;br /&gt; 23    );&lt;br /&gt; 24   end loop;&lt;br /&gt; 25  end;&lt;br /&gt; 26  /&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;Since there will be no SQL executed, I've decided to simply measure wall clock time, given that we can obtain latch wait information from the extended SQL trace:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table results&lt;br /&gt;  2  (&lt;br /&gt;  3   sid number,&lt;br /&gt;  4   cs number&lt;br /&gt;  5  );&lt;br /&gt; &lt;br /&gt;Table created&lt;/pre&gt;Here is the test procedure itself:&lt;br /&gt;&lt;pre&gt;SQL&gt; create or replace procedure test_dra(&lt;br /&gt;  2   p_i in number&lt;br /&gt;  3  ) is&lt;br /&gt;  4   l_age  sys.re$variable_value;&lt;br /&gt;  5   l_balance sys.re$variable_value;&lt;br /&gt;  6   l_birthdate sys.re$variable_value;&lt;br /&gt;  7   l_today  sys.re$variable_value;&lt;br /&gt;  8   l_vvl  sys.re$variable_value_list;&lt;br /&gt;  9   l_true  sys.re$rule_hit_list;&lt;br /&gt; 10   l_maybe  sys.re$rule_hit_list;&lt;br /&gt; 11   l_time  number;&lt;br /&gt; 12  begin&lt;br /&gt; 13   l_age:=sys.re$variable_value('age', anydata.convertnumber(16));&lt;br /&gt; 14   l_balance:=sys.re$variable_value('balance', anydata.convertnumber(1500));&lt;br /&gt; 15   l_birthdate:=sys.re$variable_value('birthdate', anydata.convertdate(to_date('20090101', 'yyyymmdd')));&lt;br /&gt; 16   l_today:=sys.re$variable_value('today', anydata.convertdate(trunc(sysdate)));&lt;br /&gt; 17  &lt;br /&gt; 18   l_vvl:=sys.re$variable_value_list(l_age, l_balance, l_birthdate, l_today);&lt;br /&gt; 19  &lt;br /&gt; 20   l_true:=sys.re$rule_hit_list();&lt;br /&gt; 21   l_maybe:=sys.re$rule_hit_list();&lt;br /&gt; 22  &lt;br /&gt; 23   l_time:=dbms_utility.get_time;&lt;br /&gt; 24   dbms_monitor.session_trace_enable(waits =&gt; true, binds =&gt; false);&lt;br /&gt; 25  &lt;br /&gt; 26   for i in 1 .. p_i&lt;br /&gt; 27   loop&lt;br /&gt; 28    dbms_rule.evaluate(&lt;br /&gt; 29     rule_set_name =&gt; 'adverts',&lt;br /&gt; 30     evaluation_context =&gt; 'adverts_ctx',&lt;br /&gt; 31     variable_values =&gt; l_vvl,&lt;br /&gt; 32     stop_on_first_hit =&gt; false,&lt;br /&gt; 33     true_rules =&gt; l_true,&lt;br /&gt; 34     maybe_rules =&gt; l_maybe&lt;br /&gt; 35    );&lt;br /&gt; 36   end loop;&lt;br /&gt; 37  &lt;br /&gt; 38   dbms_monitor.session_trace_disable;&lt;br /&gt; 39   insert into results values (sys_context('userenv', 'sid'), dbms_utility.get_time-l_time);&lt;br /&gt; 40  end test_dra;&lt;br /&gt; 41  /&lt;br /&gt; &lt;br /&gt;Procedure created&lt;/pre&gt;As before, I've used four parallel jobs with 100000 iterations each. Here are the results I've got:&lt;br /&gt;&lt;pre&gt;SQL&gt; select * from results;&lt;br /&gt; &lt;br /&gt;       SID         CS&lt;br /&gt;---------- ----------&lt;br /&gt;       134       3472&lt;br /&gt;       131       3412&lt;br /&gt;       133       3205&lt;br /&gt;       132       3358 &lt;/pre&gt;And tkprof of one of the jobs:&lt;br /&gt;&lt;pre&gt;OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        0      0.00       0.00          0          0          0           0&lt;br /&gt;Execute      0      0.00       0.00          0          0          0           0&lt;br /&gt;Fetch        0      0.00       0.00          0          0          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total        0      0.00       0.00          0          0          0           0&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  library cache: mutex X                        769        0.00          0.04&lt;br /&gt;  latch free                                     39        0.00          0.00&lt;/pre&gt;Which is, well, about 6.5 times slower given wall clock time compared to the cached results using package from my previous blog post:&lt;br /&gt;&lt;pre&gt;OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        3      0.00       0.00          0          0          0           0&lt;br /&gt;Execute 300000      4.85       4.97          0          0          0      300000&lt;br /&gt;Fetch        0      0.00       0.00          0          0          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total   300003      4.85       4.97          0          0          0      300000&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  library cache: mutex X                        209        0.00          0.00&lt;br /&gt;  cursor: pin S wait on X                        98        0.01          1.04&lt;br /&gt;  cursor: pin S                                 360        0.00          0.00&lt;/pre&gt;The interesting thing to note here is that &lt;span style="font-style:italic;"&gt;DBMS_RULE_ADM&lt;/span&gt; seems to have a better &lt;span style="font-style:italic;"&gt;scalability&lt;/span&gt;, however, I can't really say at what point it could be justified (if at all) given a pretty hefty wall clock time difference.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-3151972004631731731?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/using-dbmsruleadm-to-evaluate-dynamic.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-6343985583743782674</guid><pubDate>Sun, 11 Jan 2009 01:44:00 +0000</pubDate><atom:updated>2009-01-10T20:53:59.195-05:00</atom:updated><title>Result Cache and simple queries</title><description>I don't really want to beat the &lt;a href="http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:676698900346506951"&gt;dead horse&lt;/a&gt; here, however, I came through an interesting observation today.&lt;br /&gt;&lt;br /&gt;It turned out that the latest version of APEX (3.1.2, or maybe some oldest versions as well, don't have these to check out) does use &lt;span style="font-style:italic;"&gt;result_cache&lt;/span&gt; hint for a queries like this:&lt;br /&gt;&lt;pre&gt;SELECT /*+ result_cache */ MESSAGE_TEXT&lt;br /&gt; FROM WWV_FLOW_MESSAGES$&lt;br /&gt; WHERE SECURITY_GROUP_ID = 10&lt;br /&gt;  AND MESSAGE_LANGUAGE = :B2&lt;br /&gt;  AND NAME = UPPER(:B1 )&lt;br /&gt;&lt;br /&gt;SELECT /*+ result_cache */ NLS_LANGUAGE, NLS_TERRITORY, NLS_SORT, NLS_WINDOWS_CHARSET&lt;br /&gt; FROM WWV_FLOW_LANGUAGES&lt;br /&gt; WHERE LANG_ID_UPPER = UPPER(:B1 )&lt;/pre&gt;Both of these queries are simple lookups...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-6343985583743782674?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/result-cache-and-simple-queries.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-7926769643027962898</guid><pubDate>Thu, 08 Jan 2009 01:30:00 +0000</pubDate><atom:updated>2009-01-08T13:37:01.543-05:00</atom:updated><title>Searching over structured and unstructured data in 10G</title><description>In my previous &lt;a href="http://afatkulin.blogspot.com/2009/01/searching-over-structured-and.html"&gt;blog post&lt;/a&gt; I've explained a nice little feature introduced to Oracle Text Context indexes in 11G which allows you to do efficient searches involving both structured and unstructured data. It is time to talk about what could be done by those of you using previous Oracle versions.&lt;br /&gt;&lt;br /&gt;I'll start with the same table and data which I've used in my previous blog post.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Tag it&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Literally. Because the best way to search over structured and unstructured data is, well, not to mix it. I'll show you how to use your own datastore procedure to archive this goal.&lt;br /&gt;&lt;br /&gt;First of all, we need to declare an author tag, we will use it for our searches later:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL&gt; begin&lt;br /&gt;  2   ctx_ddl.create_section_group('books_section_group', 'basic_section_group');&lt;br /&gt;  3   ctx_ddl.add_field_section('books_section_group', 'author', 'author', false);&lt;br /&gt;  4  end;&lt;br /&gt;  5  /&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;All we need after that is a simple custom datastore procedure to merge it all together (this function is what Oracle Text will index as well) and tag the author:&lt;br /&gt;&lt;pre&gt;SQL&gt; create or replace procedure books_ds(&lt;br /&gt;  2   p_rid in rowid,&lt;br /&gt;  3     p_clob in out clob&lt;br /&gt;  4  ) is&lt;br /&gt;  5  begin&lt;br /&gt;  6   for cur in (select '&amp;lt;author&amp;gt;'||author||'&amp;lt;/author&amp;gt;' author, text from books where rowid=p_rid)&lt;br /&gt;  7   loop&lt;br /&gt;  8    dbms_lob.copy(p_clob, cur.author, dbms_lob.getlength(cur.author));&lt;br /&gt;  9    dbms_lob.append(p_clob, cur.text);&lt;br /&gt; 10   end loop;&lt;br /&gt; 11  end;&lt;br /&gt; 12  /&lt;br /&gt; &lt;br /&gt;Procedure created&lt;/pre&gt;Let's create an index now:&lt;br /&gt;&lt;pre&gt;SQL&gt; alter table books add (books_info varchar2(1));&lt;br /&gt; &lt;br /&gt;Table altered&lt;br /&gt;&lt;br /&gt;SQL&gt; begin&lt;br /&gt;  2   ctx_ddl.create_preference('books_ds', 'user_datastore');&lt;br /&gt;  3   ctx_ddl.set_attribute('books_ds', 'procedure', 'books_ds');&lt;br /&gt;  4  end;&lt;br /&gt;  5  /&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;br /&gt;&lt;br /&gt;SQL&gt; CREATE INDEX ctx_books_info&lt;br /&gt;  2    ON books (books_info)&lt;br /&gt;  3    INDEXTYPE IS CTXSYS.CONTEXT&lt;br /&gt;  4    PARAMETERS('filter ctxsys.null_filter section group books_section_group lexer books_lexer datastore books_ds memory 64m');&lt;br /&gt; &lt;br /&gt;Index created&lt;/pre&gt;Continuing on the example I've used in my previous post, searching on books which mention procedures and are written by author XDB will look like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; select author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(books_info, 'xdb within author and procedure', 1) &gt; 0&lt;br /&gt;  4   order by score(1) desc;&lt;br /&gt;&lt;br /&gt;37 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         13  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;         40  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;       1379  bytes sent via SQL*Net to client&lt;br /&gt;        372  bytes received via SQL*Net from client&lt;br /&gt;          4  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;         37  rows processed&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-7926769643027962898?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/searching-over-structured-and_07.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-2919661874873511865</guid><pubDate>Sun, 04 Jan 2009 01:39:00 +0000</pubDate><atom:updated>2009-01-04T11:08:33.147-05:00</atom:updated><title>Maintaining summaries in a highly concurrent fashion</title><description>I was involved in designing a highly-concurrent OLTP system last year (more than 300K users during peak hours) which were allowing users to play online and win some prizes. As part of the prize winning logic we had to maintain count of prizes won in order to be able to display it in real-time.&lt;br /&gt;&lt;br /&gt;The table contained won prizes looked basically like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table winners&lt;br /&gt;  2  (&lt;br /&gt;  3   prize_id number primary key,&lt;br /&gt;  4   user_id  number&lt;br /&gt;  5  );&lt;br /&gt; &lt;br /&gt;Table created&lt;/pre&gt;That is -- we were storing prize_id among with the user_id who won it. There were a couple of millions prizes available for winning. You can accomplish won prize counting in a number of ways:&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Count it each time.&lt;br /&gt;Do this only if you partner up with the same folks who sell you hardware and Oracle CPU licenses.&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Create on-commit materialized view with count.&lt;br /&gt;Though much better than previous KIWI'ish approach, it brings a bit of overhead for maintaining a materialized view log plus, if you don't take special cares, may blocks users during commit. Nevertheless, this is usually a valid approach, especially if you want to leverage features like query rewrite. We didn't really need it and, to make things more interesting, someone was winning a prize every so milliseconds. Do simple things (insert into winners, mview log, update summaries table, delete from mview log) a lot of times and wander how quickly it starts to add up.&lt;br /&gt;&lt;br /&gt;&lt;li&gt;Maintain summaries yourself.&lt;br /&gt;This is relatively easy to do, especially when application does nothing but calls a set of PL/SQL APIs (which means you can do whatever you want to archive the required output).&lt;/ul&gt;However, doing a simple &lt;span style="font-style:italic;"&gt;update winners_cnt set cnt=cnt+1&lt;/span&gt; is going to serialize all the winners so we need an easy way to spread stuff out. The simplest, yet very efficient, way to archive this is to do something like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table winners_cnt_t&lt;br /&gt;  2  (&lt;br /&gt;  3   slot_id number  primary key,&lt;br /&gt;  4   cnt  number&lt;br /&gt;  5  ) organization index;&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt;&lt;br /&gt;SQL&gt; insert into winners_cnt_t&lt;br /&gt;  2   select level-1, 0&lt;br /&gt;  3    from dual&lt;br /&gt;  4    connect by level &lt;= 11;&lt;br /&gt; &lt;br /&gt;11 rows inserted&lt;br /&gt; &lt;br /&gt;SQL&gt; commit;&lt;br /&gt; &lt;br /&gt;Commit complete&lt;/pre&gt;Make sure your slots count is a prime number. Then you can hide it behind the view to make things transparent:&lt;br /&gt;&lt;pre&gt;create view winners_cnt as&lt;br /&gt; select sum(cnt) cnt&lt;br /&gt;  from winners_cnt_t;&lt;/pre&gt;After that, all you have to do is maintain number of winners the following way...&lt;br /&gt;&lt;pre&gt;update winners_cnt_t&lt;br /&gt; set cnt=cnt+1&lt;br /&gt; where slot_id=mod(p_prize_id, 11);&lt;/pre&gt;...which will distribute updates evenly among all rows in the table. You can vary number of slots to match your degree of concurrency.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-2919661874873511865?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/maintaining-summaries-in-highly.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8757826140451382976</guid><pubDate>Sat, 03 Jan 2009 14:53:00 +0000</pubDate><atom:updated>2009-01-03T15:27:16.072-05:00</atom:updated><title>11G adaptive direct path reads -- what is the cached/dirty blocks threshold?</title><description>11G's ability to do direct path reads during full table scans without utilizing PQ was covered in a number of places already (see &lt;a href="http://oracledoug.com/serendipity/index.php?/archives/1321-11g-and-direct-path-reads.html"&gt;this post&lt;/a&gt; by Doug Burns for example).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;When direct path reads starts to happen?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It is known that somewhat reliable figure is your _small_table_threshold multiplied by 5 (mentioned by Tanel Poder on oracle-l recently). You can discover it using quick and dirty test case similar to this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create tablespace adr_test datafile size 64m segment space management manual;&lt;br /&gt; &lt;br /&gt;Tablespace created&lt;br /&gt;&lt;br /&gt;SQL&gt; create table t (v varchar2(100)) pctused 1 pctfree 99 tablespace adr_test;&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt;&lt;br /&gt;SQL&gt; create or replace function get_adr_trsh(&lt;br /&gt;  2   p_step  in number,&lt;br /&gt;  3   p_start  in number default 0,&lt;br /&gt;  4   p_stop  in number default null&lt;br /&gt;  5  ) return number is&lt;br /&gt;  6   l_prd  number;&lt;br /&gt;  7   l_blocks number:=0;&lt;br /&gt;  8   l_start  number:=p_start;&lt;br /&gt;  9  begin&lt;br /&gt; 10   execute immediate 'truncate table t';&lt;br /&gt; 11  &lt;br /&gt; 12   loop&lt;br /&gt; 13    insert /*+ append */ into t&lt;br /&gt; 14     select rpad('*', 100, '*')&lt;br /&gt; 15      from dual&lt;br /&gt; 16      connect by level &lt;= p_step + l_start;&lt;br /&gt; 17    commit;&lt;br /&gt; 18  &lt;br /&gt; 19    l_blocks:=l_blocks + p_step + l_start;&lt;br /&gt; 20    l_start:=0;&lt;br /&gt; 21  &lt;br /&gt; 22    execute immediate 'alter system flush buffer_cache';&lt;br /&gt; 23  &lt;br /&gt; 24    select /*+ full(t) */ count(*) into l_cnt from t;&lt;br /&gt; 25  &lt;br /&gt; 26    select value into l_prd&lt;br /&gt; 27     from v$segment_statistics&lt;br /&gt; 28     where owner=user&lt;br /&gt; 29      and object_name='T'&lt;br /&gt; 30      and statistic_name='physical reads direct';&lt;br /&gt; 31  &lt;br /&gt; 32    exit when (l_prd &gt; 0 or l_blocks &gt; nvl(p_stop, l_blocks));&lt;br /&gt; 33  &lt;br /&gt; 34   end loop;&lt;br /&gt; 35  &lt;br /&gt; 36   return l_blocks - p_step;&lt;br /&gt; 37  end;&lt;br /&gt; 38  /&lt;br /&gt; &lt;br /&gt;Function created&lt;br /&gt;&lt;/pre&gt;My _small_table_threshold is:&lt;br /&gt;&lt;pre&gt;SQL&gt; select ksppstvl&lt;br /&gt;  2   from x$ksppi x, x$ksppcv y&lt;br /&gt;  3   where (x.indx = y.indx)&lt;br /&gt;  4    and ksppinm='_small_table_threshold';&lt;br /&gt; &lt;br /&gt;KSPPSTVL&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;314&lt;/pre&gt; which is about 2% of my buffer cache (128MB) so you may expect 11G switch to direct path reads once table goes beyond 1570 blocks. Let's check it:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2   l_trsh number;&lt;br /&gt;  3  begin&lt;br /&gt;  4   l_trsh:=get_adr_trsh(10, 1500, 2000);&lt;br /&gt;  5  &lt;br /&gt;  6   dbms_output.put_line(l_trsh);&lt;br /&gt;  7  end;&lt;br /&gt;  8  /&lt;br /&gt; &lt;br /&gt;1570&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;Note that that number is somewhat "about" and you can get different results depending on stuff like using ASSM/MSSM.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What is the cached blocks threshold?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Direct path reads stops happening after certain amount of your table's blocks are in the buffer cache already. Discovering it is fairly easy as well:&lt;br /&gt;&lt;pre&gt;SQL&gt; --need this so we can do irs and cache table blocks&lt;br /&gt;SQL&gt; create index i_t on t (1);&lt;br /&gt; &lt;br /&gt;Index created&lt;br /&gt;&lt;br /&gt;SQL&gt; create or replace function get_cached_trsh(&lt;br /&gt;  2    p_start in number default 0,&lt;br /&gt;  3    p_step in number default 1&lt;br /&gt;  4  ) return number is&lt;br /&gt;  5   cursor l_cur is select /*+ index(t i_t) */ * from t;&lt;br /&gt;  6   l_v varchar2(100);&lt;br /&gt;  7   l_trsh number:=0;&lt;br /&gt;  8   l_prd number:=0;&lt;br /&gt;  9   l_cnt number:=0;&lt;br /&gt; 10   l_start number:=p_start;&lt;br /&gt; 11  begin&lt;br /&gt; 12   execute immediate 'alter system flush buffer_cache';&lt;br /&gt; 13   open l_cur;&lt;br /&gt; 14  &lt;br /&gt; 15   loop&lt;br /&gt; 16    for i in 1 .. p_step+l_start&lt;br /&gt; 17    loop&lt;br /&gt; 18     fetch l_cur into l_v;&lt;br /&gt; 19    end loop;&lt;br /&gt; 20    l_trsh:=l_trsh+p_step+l_start;&lt;br /&gt; 21    l_start:=0;&lt;br /&gt; 22  &lt;br /&gt; 23    select /*+ full(t) */ count(*) into l_cnt from t;&lt;br /&gt; 24  &lt;br /&gt; 25    select value into l_cnt&lt;br /&gt; 26     from v$segment_statistics&lt;br /&gt; 27     where owner=user&lt;br /&gt; 28      and object_name='T'&lt;br /&gt; 29      and statistic_name='physical reads direct';&lt;br /&gt; 30  &lt;br /&gt; 31    exit when l_cnt=l_prd or l_cur%notfound;&lt;br /&gt; 32  &lt;br /&gt; 33    l_prd:=l_cnt;&lt;br /&gt; 34  &lt;br /&gt; 35   end loop;&lt;br /&gt; 36  &lt;br /&gt; 37   close l_cur;&lt;br /&gt; 38   return l_trsh;&lt;br /&gt; 39  end;&lt;br /&gt; 40  /&lt;br /&gt; &lt;br /&gt;Function created&lt;/pre&gt;Now, we can see after how many blocks 11G will stop doing direct path reads:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2   l_trsh number;&lt;br /&gt;  3  begin&lt;br /&gt;  4   l_trsh:=get_cached_trsh(500, 1);&lt;br /&gt;  5  &lt;br /&gt;  6   dbms_output.put_line(l_trsh);&lt;br /&gt;  7  end;&lt;br /&gt;  8  /&lt;br /&gt; &lt;br /&gt;789&lt;br /&gt; &lt;br /&gt;PL/SQL procedure successfully completed&lt;/pre&gt;Which happens to be half of the table's blocks. I've repeated the above test with 256MB buffer cache and got 3140 blocks (number of blocks for direct read to start happening) and  1568 (number of cached blocks) respectively. Please note that cached blocks threshold seems to be &lt;span style="font-style:italic;"&gt;not&lt;/span&gt; dependent on your buffer cache size (to a degree where it can find space of course).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What is the dirty blocks threshold?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Doing direct path reads requires segment level checkpoint which may not be something you would like to do if you have a lot of these for the sake of direct read alone.&lt;br /&gt;&lt;br /&gt;Something we can start with:&lt;br /&gt;&lt;pre&gt;SQL&gt; create or replace function get_dirty_trsh(&lt;br /&gt;  2    p_step in number,&lt;br /&gt;  3    p_start in number default 0,&lt;br /&gt;  4    p_stop in number default null&lt;br /&gt;  5  ) return number is&lt;br /&gt;  6   l_trsh number:=0;&lt;br /&gt;  7   l_prd number:=0;&lt;br /&gt;  8   l_cnt number:=0;&lt;br /&gt;  9   l_start number:=p_start;&lt;br /&gt; 10  begin&lt;br /&gt; 11   execute immediate 'alter system flush buffer_cache';&lt;br /&gt; 12  &lt;br /&gt; 13   loop&lt;br /&gt; 14    l_trsh:=l_trsh+p_step+l_start;&lt;br /&gt; 15    update t set v=v where rownum &lt;= l_trsh;&lt;br /&gt; 16    commit;&lt;br /&gt; 17    l_start:=0;&lt;br /&gt; 18  &lt;br /&gt; 19    select /*+ full(t) */ count(*) into l_cnt from t;&lt;br /&gt; 20  &lt;br /&gt; 21    select value into l_cnt&lt;br /&gt; 22     from v$segment_statistics&lt;br /&gt; 23     where owner=user&lt;br /&gt; 24      and object_name='T'&lt;br /&gt; 25      and statistic_name='physical reads direct';&lt;br /&gt; 26  &lt;br /&gt; 27    exit when l_cnt=l_prd or l_trsh &gt; nvl(p_stop, l_trsh);&lt;br /&gt; 28  &lt;br /&gt; 29    l_prd:=l_cnt;&lt;br /&gt; 30  &lt;br /&gt; 31   end loop;&lt;br /&gt; 32  &lt;br /&gt; 33   return l_trsh;&lt;br /&gt; 34  end;&lt;br /&gt; 35  /&lt;br /&gt; &lt;br /&gt;Function created&lt;br /&gt;&lt;br /&gt;SQL&gt; declare&lt;br /&gt;  2   l_trsh number;&lt;br /&gt;  3  begin&lt;br /&gt;  4   l_trsh:=get_dirty_trsh(1, 350, 400);&lt;br /&gt;  5  &lt;br /&gt;  6   dbms_output.put_line(l_trsh);&lt;br /&gt;  7  end;&lt;br /&gt;  8  /&lt;br /&gt;384&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;/pre&gt;Which turns out to be 1/4 of a table size.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Quick and dirty&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Please note that adaptive direct path reads could (&lt;span style="font-style:italic;"&gt;and most probably do&lt;/span&gt;) have much more variables to make a decision. The above test were done using ad-hoc approach to at least have an idea what could be potential factors there. Things like system statistics, tablespace block sizes, delayed blocks cleanouts, etc. has a potential to interfere over there.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL&gt; select * from v$version;&lt;br /&gt; &lt;br /&gt;BANNER&lt;br /&gt;--------------------------------------------------------------------------------&lt;br /&gt;Oracle Database 11g Enterprise Edition Release 11.1.0.7.0 - 64bit Production&lt;br /&gt;PL/SQL Release 11.1.0.7.0 - Production&lt;br /&gt;CORE 11.1.0.7.0 Production&lt;br /&gt;TNS for 64-bit Windows: Version 11.1.0.7.0 - Production&lt;br /&gt;NLSRTL Version 11.1.0.7.0 - Production&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8757826140451382976?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/11g-adaptive-direct-path-reads-what-is.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">4</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-3784291946644953286</guid><pubDate>Fri, 02 Jan 2009 02:55:00 +0000</pubDate><atom:updated>2009-01-02T01:51:53.902-05:00</atom:updated><title>Scaling dynamic SQL</title><description>&lt;span style="font-weight:bold;"&gt;The story&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I was working for one of the Russian biggest telcos back then and marketing guys came out with their next idea. One of the most frequent things your customers do is checking their balances. The idea was to attach an advertising to each balance response. However, usually you don't really want to attach some static message that you show to everyone because it is not the way your customers care about it. Think of Google, which became so popular because it prioritizes information by &lt;span style="font-style:italic;"&gt;relevance&lt;/span&gt;. Doing something useless to your customers not only annoys them but wastes your bandwidth as well.&lt;br /&gt;&lt;br /&gt;What you have to do is targeted adverts. What that means is that you may substantially increase the value by figuring out what your customers would like to see based on a data about them which you have in your billing system. And you know literally hundreds of things about any single customer. Starting from age, martial status, birthday, active services and ending up with approximate location based on triangulation data. Think of Google's sponsored links.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The system&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Upon receiving a balance request, the system had to find out various stuff about requester and then figure out what to show based on the rules from a marketing department. Rules had to offer virtually unlimited flexibility and the system itself had to be flexible to introduce and remove targeting conditions on the fly.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The implementation&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;What we had is a simple table with a PL/SQL predicates, like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table p&lt;br /&gt;  2  (&lt;br /&gt;  3   p_id number primary key,&lt;br /&gt;  4   p varchar2(4000)&lt;br /&gt;  5  );&lt;br /&gt; &lt;br /&gt;Table created&lt;br /&gt;&lt;br /&gt;SQL&gt; insert into p values (1, 'to_number(:age) between 16 and 18');&lt;br /&gt; &lt;br /&gt;1 row inserted&lt;br /&gt;SQL&gt; insert into p values (2, 'to_date(:birthday, ''yyyymmdd'')=trunc(sysdate) or :balance &gt; 1000');&lt;br /&gt; &lt;br /&gt;1 row inserted&lt;br /&gt;SQL&gt; insert into p values (3, 'to_number(:balance) between 100 and 200 and :age &gt; 18');&lt;br /&gt; &lt;br /&gt;1 row inserted&lt;br /&gt; &lt;br /&gt;SQL&gt; commit;&lt;br /&gt; &lt;br /&gt;Commit complete&lt;/pre&gt;Of course, in reality it was a bit more complex than that, allowing for prioritizing, schedules, etc. As you'd probably already guessed, upon receiving a balance request we were binding data about customer into the above predicates to figure out which one evaluates to true which in turn lead to relevant advert. Given that you may be getting around 5000 balance requests each second plus you have to multiply this by a number of predicates you have to check, you already talking about tens of thousands evaluations per second. In other words, you have to make the entire thing pretty darn efficient.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Soft parsing&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Soft parsing is something which is deemed by many to be inevitable as soon as you have to deal with stuff like above -- storing dynamically generated PL/SQL predicates in a table and executing these during runtime. And this is something we had to avoid because, even with stuff like session_cached_cursor, parse is still a parse, a scalability inhibitor and CPU waster. Think of not using KIWI.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What to do?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I'll show you a little trick you can use to break the dreaded marriage of dynamic SQL with parsing. Take a look at the following package:&lt;br /&gt;&lt;pre&gt;create or replace package eval is&lt;br /&gt;&lt;br /&gt; type t_cached_cursors is table of number index by p.p%type;&lt;br /&gt; g_cached_cursors t_cached_cursors;&lt;br /&gt;&lt;br /&gt; function evaluate(&lt;br /&gt;  p in p.p%type,&lt;br /&gt;  p_n in dbms_sql.Varchar2_Table,&lt;br /&gt;  p_v in dbms_sql.Varchar2_Table&lt;br /&gt; ) return boolean;&lt;br /&gt;&lt;br /&gt; function evaluate_nc(&lt;br /&gt;  p in p.p%type,&lt;br /&gt;  p_n in dbms_sql.Varchar2_Table,&lt;br /&gt;  p_v in dbms_sql.Varchar2_Table&lt;br /&gt; ) return boolean;&lt;br /&gt;end eval;&lt;br /&gt;&lt;br /&gt;create or replace package body eval is&lt;br /&gt;&lt;br /&gt;function evaluate(&lt;br /&gt; p in p.p%type,&lt;br /&gt; p_n in dbms_sql.Varchar2_Table,&lt;br /&gt; p_v in dbms_sql.Varchar2_Table&lt;br /&gt;) return boolean is&lt;br /&gt; l_cursor number;&lt;br /&gt; l_res  number;&lt;br /&gt;begin&lt;br /&gt; begin&lt;br /&gt;  l_cursor:=g_cached_cursors(p);&lt;br /&gt; exception when no_data_found then&lt;br /&gt;  l_cursor:=dbms_sql.open_cursor;&lt;br /&gt;  dbms_sql.parse(l_cursor, p, dbms_sql.native);&lt;br /&gt;  g_cached_cursors(p):=l_cursor;&lt;br /&gt; end;&lt;br /&gt; dbms_sql.bind_variable(l_cursor, 'l_res', l_res);&lt;br /&gt;&lt;br /&gt; for i in 1 .. p_n.count&lt;br /&gt; loop&lt;br /&gt;  if (instr(p, ':'||p_n(i)) &gt; 0)&lt;br /&gt;  then&lt;br /&gt;   dbms_sql.bind_variable(l_cursor, p_n(i), p_v(i));&lt;br /&gt;  end if;&lt;br /&gt; end loop;&lt;br /&gt;&lt;br /&gt; l_res:=dbms_sql.execute(l_cursor);&lt;br /&gt; dbms_sql.variable_value(l_cursor, 'l_res', l_res);&lt;br /&gt;&lt;br /&gt; return (l_res=1);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;function evaluate_nc(&lt;br /&gt; p in p.p%type,&lt;br /&gt; p_n in dbms_sql.Varchar2_Table,&lt;br /&gt; p_v in dbms_sql.Varchar2_Table&lt;br /&gt;) return boolean is&lt;br /&gt; l_cursor number;&lt;br /&gt; l_res  number;&lt;br /&gt;begin&lt;br /&gt; l_cursor:=dbms_sql.open_cursor;&lt;br /&gt; dbms_sql.parse(l_cursor, p, dbms_sql.native);&lt;br /&gt; dbms_sql.bind_variable(l_cursor, 'l_res', l_res);&lt;br /&gt;&lt;br /&gt; for i in 1 .. p_n.count&lt;br /&gt; loop&lt;br /&gt;  if (instr(p, ':'||p_n(i)) &gt; 0)&lt;br /&gt;  then&lt;br /&gt;   dbms_sql.bind_variable(l_cursor, p_n(i), p_v(i));&lt;br /&gt;  end if;&lt;br /&gt; end loop;&lt;br /&gt;&lt;br /&gt; l_res:=dbms_sql.execute(l_cursor);&lt;br /&gt; dbms_sql.variable_value(l_cursor, 'l_res', l_res);&lt;br /&gt; dbms_sql.close_cursor(l_cursor);&lt;br /&gt;&lt;br /&gt; return (l_res=1);&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;end eval;&lt;/pre&gt;The package has two functions -- evaluate and evaluate_nc, the first one is using a simple caching trick. The idea behind evaluate function is really that simple -- upon opening and parsing a cursor, place it into in-memory table indexed by cursor text for further reuse instead of closing it. Each execution peeks at that in-memory table to see if there is a cursor we can reuse instead of going through the whole parsing exercise.&lt;br /&gt;&lt;br /&gt;The usage is simple as well:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2   l_n  dbms_sql.varchar2_table;&lt;br /&gt;  3   l_v  dbms_sql.varchar2_table;&lt;br /&gt;  4   l_res boolean;&lt;br /&gt;  5  begin&lt;br /&gt;  6   l_n(1):='age';&lt;br /&gt;  7   l_n(2):='birthday';&lt;br /&gt;  8   l_n(3):='balance';&lt;br /&gt;  9   l_v(1):='16';&lt;br /&gt; 10   l_v(2):='20090101';&lt;br /&gt; 11   l_v(3):='1500';&lt;br /&gt; 12  &lt;br /&gt; 13   for cur in (select p_id, 'declare l_res number; begin :l_res:=case when ('||p||') then 1 else &lt;br /&gt;0 end; end;' p from p)&lt;br /&gt; 14   loop&lt;br /&gt; 15    l_res:=eval.evaluate(cur.p, l_n, l_v);&lt;br /&gt; 16    dbms_output.put_line('eval p'||to_char(cur.p_id)||': '||case when l_res then 'true' else 'fal&lt;br /&gt;se' end);&lt;br /&gt; 17   end loop;&lt;br /&gt; 18  end;&lt;br /&gt; 19  /&lt;br /&gt;eval p1: true&lt;br /&gt;eval p2: true&lt;br /&gt;eval p3: false&lt;/pre&gt;Let's do some tests now. Here are two test procedures:&lt;br /&gt;&lt;pre&gt;create or replace procedure test_cached(&lt;br /&gt; p_i in number&lt;br /&gt;) is&lt;br /&gt; l_p   dbms_sql.Varchar2_Table;&lt;br /&gt; l_n   dbms_sql.varchar2_table;&lt;br /&gt; l_v   dbms_sql.varchar2_table;&lt;br /&gt; l_res  boolean;&lt;br /&gt;begin&lt;br /&gt; l_n(1):='age';&lt;br /&gt; l_n(2):='birthday';&lt;br /&gt; l_n(3):='balance';&lt;br /&gt;&lt;br /&gt; l_v(1):='100';&lt;br /&gt; l_v(2):='20090101';&lt;br /&gt; l_v(3):='1000';&lt;br /&gt;&lt;br /&gt; select 'declare /* cached */ l_res number; begin :l_res:=case when ('||p||') then 1 else 0 end; end;'&lt;br /&gt;  bulk collect into l_p&lt;br /&gt;  from p;&lt;br /&gt;&lt;br /&gt; dbms_monitor.session_trace_enable(waits =&gt; true, binds =&gt; false);&lt;br /&gt; for i in 1 .. p_i&lt;br /&gt; loop&lt;br /&gt;  for j in 1 .. l_p.count&lt;br /&gt;  loop&lt;br /&gt;   l_res:=eval.evaluate(l_p(j), l_n, l_v);&lt;br /&gt;  end loop;&lt;br /&gt; end loop;&lt;br /&gt; dbms_monitor.session_trace_disable;&lt;br /&gt;end;&lt;br /&gt;&lt;br /&gt;create or replace procedure test_not_cached(&lt;br /&gt; p_i in number&lt;br /&gt;) is&lt;br /&gt; l_p   dbms_sql.Varchar2_Table;&lt;br /&gt; l_n   dbms_sql.varchar2_table;&lt;br /&gt; l_v   dbms_sql.varchar2_table;&lt;br /&gt; l_res  boolean;&lt;br /&gt;begin&lt;br /&gt; l_n(1):='age';&lt;br /&gt; l_n(2):='birthday';&lt;br /&gt; l_n(3):='balance';&lt;br /&gt;&lt;br /&gt; l_v(1):='100';&lt;br /&gt; l_v(2):='20090101';&lt;br /&gt; l_v(3):='1000';&lt;br /&gt;&lt;br /&gt; select 'declare /* not_cached */ l_res number; begin :l_res:=case when ('||p||') then 1 else 0 end; end;'&lt;br /&gt;  bulk collect into l_p&lt;br /&gt;  from p;&lt;br /&gt;&lt;br /&gt; dbms_monitor.session_trace_enable(waits =&gt; true, binds =&gt; false);&lt;br /&gt; for i in 1 .. p_i&lt;br /&gt; loop&lt;br /&gt;  for j in 1 .. l_p.count&lt;br /&gt;  loop&lt;br /&gt;   l_res:=eval.evaluate_nc(l_p(j), l_n, l_v);&lt;br /&gt;  end loop;&lt;br /&gt; end loop;&lt;br /&gt; dbms_monitor.session_trace_disable;&lt;br /&gt;end;&lt;/pre&gt;I tested each of these using four parallel jobs with 100000 iterations (p_i parameter) each. Here is what I've got (I'm using one of the predicates as an example):&lt;br /&gt;&lt;pre&gt;declare /* cached */ l_res number; begin :l_res:=case when (to_number(:age) &lt;br /&gt;  between 16 and 18) then 1 else 0 end; end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse        1      0.00       0.00          0          0          0           0&lt;br /&gt;Execute 100000      1.40       1.29          0          0          0      100000&lt;br /&gt;Fetch        0      0.00       0.00          0          0          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total   100001      1.40       1.29          0          0          0      100000&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 0&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 40     (recursive depth: 2)&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  cursor: pin S wait on X                        35        0.01          0.37&lt;br /&gt;  cursor: pin S                                  91        0.00          0.00&lt;br /&gt;&lt;br /&gt;declare /* not_cached */ l_res number; begin :l_res:=case when &lt;br /&gt;  (to_number(:age) between 16 and 18) then 1 else 0 end; end;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;call     count       cpu    elapsed       disk      query    current        rows&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;Parse   100000      1.00       1.21          0          0          0           0&lt;br /&gt;Execute 100000      2.73       2.74          0          0          0      100000&lt;br /&gt;Fetch        0      0.00       0.00          0          0          0           0&lt;br /&gt;------- ------  -------- ---------- ---------- ---------- ----------  ----------&lt;br /&gt;total   200000      3.73       3.95          0          0          0      100000&lt;br /&gt;&lt;br /&gt;Misses in library cache during parse: 1&lt;br /&gt;Misses in library cache during execute: 1&lt;br /&gt;Optimizer mode: ALL_ROWS&lt;br /&gt;Parsing user id: 40     (recursive depth: 2)&lt;br /&gt;&lt;br /&gt;Elapsed times include waiting on following events:&lt;br /&gt;  Event waited on                             Times   Max. Wait  Total Waited&lt;br /&gt;  ----------------------------------------   Waited  ----------  ------------&lt;br /&gt;  library cache: mutex X                        116        0.00          0.00&lt;br /&gt;  cursor: mutex S                                24        0.00          0.00&lt;br /&gt;  cursor: pin S                                  83        0.00          0.00&lt;br /&gt;  cursor: pin S wait on X                        30        0.01          0.28&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-style:italic;"&gt;That's a three times improvement.&lt;/span&gt; Note that in first tkprof report we got only one parse, while in second one amount of parses equals executions, as we expected.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-3784291946644953286?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/scaling-dynamic-sql.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">11</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-4151240674901248662</guid><pubDate>Wed, 31 Dec 2008 14:23:00 +0000</pubDate><atom:updated>2008-12-31T14:32:38.705-05:00</atom:updated><title>Searching over structured and unstructured data in 11G</title><description>For those of you who have to deal with searches involving both structured and unstructured data, there is a small neat feature introduced to Oracle Text Context indexes in Oracle 11G which you may find to be very useful.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The example&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's say you have a books table like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; create table books&lt;br /&gt;  2  (&lt;br /&gt;  3     author          varchar2(30),&lt;br /&gt;  4     title           varchar2(30),&lt;br /&gt;  5     published       date,&lt;br /&gt;  6     text            clob&lt;br /&gt;  7  ) lob (text) store as securefile&lt;br /&gt;  8  (&lt;br /&gt;  9     enable storage in row&lt;br /&gt; 10     cache&lt;br /&gt; 11  );&lt;br /&gt;&lt;br /&gt;Table created.&lt;/pre&gt;I'm going to populate it using some test data:&lt;br /&gt;&lt;pre&gt;SQL&gt; declare&lt;br /&gt;  2     l_clob  clob;&lt;br /&gt;  3  begin&lt;br /&gt;  4     for obj_cur in (&lt;br /&gt;  5             select owner, object_name, created&lt;br /&gt;  6                     from dba_objects&lt;br /&gt;  7                     where object_type in ('PROCEDURE','PACKAGE','PACKAGE BOD&lt;br /&gt;Y','FUNCTION','TRIGGER')&lt;br /&gt;  8     ) loop&lt;br /&gt;  9             insert into books values (obj_cur.owner, obj_cur.object_name, ob&lt;br /&gt;j_cur.created, empty_clob())&lt;br /&gt; 10                     returning text into l_clob;&lt;br /&gt; 11&lt;br /&gt; 12             dbms_lob.open(l_clob, dbms_lob.lob_readwrite);&lt;br /&gt; 13&lt;br /&gt; 14             for text_cur in (&lt;br /&gt; 15                     select text&lt;br /&gt; 16                             from dba_source&lt;br /&gt; 17                             where owner=obj_cur.owner&lt;br /&gt; 18                                     and name=obj_cur.object_name&lt;br /&gt; 19                             order by type, line&lt;br /&gt; 20             ) loop&lt;br /&gt; 21                     dbms_lob.writeappend(l_clob, dbms_lob.getlength(text_cur&lt;br /&gt;.text), text_cur.text);&lt;br /&gt; 22             end loop;&lt;br /&gt; 23&lt;br /&gt; 24             dbms_lob.close(l_clob);&lt;br /&gt; 25     end loop;&lt;br /&gt; 26  end;&lt;br /&gt; 27  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;br /&gt;&lt;br /&gt;SQL&gt; commit;&lt;br /&gt;&lt;br /&gt;Commit complete.&lt;/pre&gt;What I have basically done there is dumped the source text of some of my database objects into books table. Object owner goes as an author, object_name represents a title, object creation date goes as published and, of course, the source itself goes as book's text.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The search&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Let's say I'm interested in all the books which write about procedures. To do that kind of search using Oracle Text is fairly straightforward.&lt;br /&gt;&lt;br /&gt;First of all, we want to make sure that underscore symbol goes as part of a token. It is not really required for our example, however, it is generally a good idea to do since we want stuff like DBMS_OUTPUT or DBMS_SQL appear as a single tokens instead of being split in half due to underscore symbol treated as a special character:&lt;br /&gt;&lt;pre&gt;SQL&gt; begin&lt;br /&gt;  2     ctx_ddl.create_preference('books_lexer', 'BASIC_LEXER');&lt;br /&gt;  3     ctx_ddl.set_attribute('books_lexer', 'printjoins', '_');&lt;br /&gt;  4  end;&lt;br /&gt;  5  /&lt;br /&gt;&lt;br /&gt;PL/SQL procedure successfully completed.&lt;/pre&gt;All I have to do now is to create my index:&lt;br /&gt;&lt;pre&gt;SQL&gt; CREATE INDEX ctx_books_text&lt;br /&gt;  2    ON books (text)&lt;br /&gt;  3    INDEXTYPE IS CTXSYS.CONTEXT&lt;br /&gt;  4    PARAMETERS('filter ctxsys.null_filter lexer books_lexer memory 64m');&lt;br /&gt;&lt;br /&gt;Index created.&lt;/pre&gt;And do the search:&lt;br /&gt;&lt;pre&gt;SSQL&gt; select author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(text, 'procedure', 1) &gt; 0&lt;br /&gt;  4   order by score(1) desc;&lt;br /&gt;&lt;br /&gt;622 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2979142934&lt;br /&gt;&lt;br /&gt;-------------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                    | Name           | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |&lt;br /&gt;-------------------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT             |                |   785 |  1576K|       |   352   (1)| 00:00:05 |&lt;br /&gt;|   1 |  SORT ORDER BY               |                |   785 |  1576K|  2104K|   352   (1)| 00:00:05 |&lt;br /&gt;|   2 |   TABLE ACCESS BY INDEX ROWID| BOOKS          |   785 |  1576K|       |    12   (0)| 00:00:01 |&lt;br /&gt;|*  3 |    DOMAIN INDEX              | CTX_BOOKS_TEXT |       |       |       |    12   (0)| 00:00:01 |&lt;br /&gt;-------------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   3 - access("CTXSYS"."CONTAINS"("TEXT",'procedure',1)&gt;0)&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         13  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;        486  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;      19748  bytes sent via SQL*Net to client&lt;br /&gt;        801  bytes received via SQL*Net from client&lt;br /&gt;         43  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;        622  rows processed&lt;/pre&gt;We got 622 books which took us 486 LIOs. So far so good.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Let's add something else&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Things will get a bit interesting (or complicated, depending on the perspective) once you'll try to mix both structured and unstructured data in one search. If I will take the above search, but this time I'm interested only in books written by author XDB, the search will look like this:&lt;br /&gt;&lt;pre&gt;SQL&gt; select author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(text, 'procedure', 1) &gt; 0&lt;br /&gt;  4    and author='XDB'&lt;br /&gt;  5   order by score(1) desc;&lt;br /&gt;&lt;br /&gt;37 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2979142934&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT             |                |    18 | 37026 |    13   (8)| 00:00:01 |&lt;br /&gt;|   1 |  SORT ORDER BY               |                |    18 | 37026 |    13   (8)| 00:00:01 |&lt;br /&gt;|*  2 |   TABLE ACCESS BY INDEX ROWID| BOOKS          |    18 | 37026 |    12   (0)| 00:00:01 |&lt;br /&gt;|*  3 |    DOMAIN INDEX              | CTX_BOOKS_TEXT |       |       |    12   (0)| 00:00:01 |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("AUTHOR"='XDB')&lt;br /&gt;   3 - access("CTXSYS"."CONTAINS"("TEXT",'procedure',1)&gt;0)&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         13  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;        486  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;       1448  bytes sent via SQL*Net to client&lt;br /&gt;        372  bytes received via SQL*Net from client&lt;br /&gt;          4  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;         37  rows processed&lt;/pre&gt;Note that I've got only 37 books this time, however, it took me the same amount of resources. It is not a surprise, after all, because condition on an author column went as a filter predicate. In case your table has some indexes on structured columns already:&lt;br /&gt;&lt;pre&gt;create index i_books_author on books (author);&lt;br /&gt;&lt;br /&gt;Index created.&lt;/pre&gt;You can try to search using B*Tree index:&lt;br /&gt;&lt;pre&gt;SQL&gt; select /*+ index(books i_books_author */ author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(text, 'procedure', 1) &gt; 0&lt;br /&gt;  4    and author='XDB'&lt;br /&gt;  5   order by score(1) desc;&lt;br /&gt;&lt;br /&gt;37 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 806035686&lt;br /&gt;&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT             |                |    47 | 96679 |   288   (1)| 00:00:04 |&lt;br /&gt;|   1 |  SORT ORDER BY               |                |    47 | 96679 |   288   (1)| 00:00:04 |&lt;br /&gt;|*  2 |   TABLE ACCESS BY INDEX ROWID| BOOKS          |    47 | 96679 |   287   (0)| 00:00:04 |&lt;br /&gt;|*  3 |    INDEX RANGE SCAN          | I_BOOKS_AUTHOR |    88 |       |     1   (0)| 00:00:01 |&lt;br /&gt;-----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - filter("CTXSYS"."CONTAINS"("TEXT",'procedure',1)&gt;0)&lt;br /&gt;   3 - access("AUTHOR"='XDB')&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;          9  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;        223  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;       1454  bytes sent via SQL*Net to client&lt;br /&gt;        372  bytes received via SQL*Net from client&lt;br /&gt;          4  SQL*Net roundtrips to/from client&lt;br /&gt;          1  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;         37  rows processed&lt;/pre&gt;Though a bit better, it just really went the other way around this time. We are now fetching all books written by XDB and then passing each book to Oracle Text for filtering.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The problem&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;As long as one of your structured/ustructured predicates have a good selectivity, the search using only either of indexes will produce good results, there is not much to post-filter, after all. However, if both predicates are selective only when applied together, you need something else to speed the things up. Indexes combined through BITMAP AND can produce some improvements:&lt;br /&gt;&lt;pre&gt;SQL&gt; select author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(text, 'procedure', 1) &gt; 0&lt;br /&gt;  4    and author='XDB'&lt;br /&gt;  5   order by score(1) desc;&lt;br /&gt;&lt;br /&gt;37 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 3589384316&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                         | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT                  |                |    47 | 96679 |    17   (6)| 00:00:01 |&lt;br /&gt;|   1 |  SORT ORDER BY                    |                |    47 | 96679 |    17   (6)| 00:00:01 |&lt;br /&gt;|   2 |   TABLE ACCESS BY INDEX ROWID     | BOOKS          |    47 | 96679 |    16   (0)| 00:00:01 |&lt;br /&gt;|   3 |    BITMAP CONVERSION TO ROWIDS    |                |       |       |            |          |&lt;br /&gt;|   4 |     BITMAP AND                    |                |       |       |            |          |&lt;br /&gt;|   5 |      BITMAP CONVERSION FROM ROWIDS|                |       |       |            |          |&lt;br /&gt;|*  6 |       INDEX RANGE SCAN            | I_BOOKS_AUTHOR |       |       |     1   (0)| 00:00:01 |&lt;br /&gt;|   7 |      BITMAP CONVERSION FROM ROWIDS|                |       |       |            |          |&lt;br /&gt;|   8 |       SORT ORDER BY               |                |       |       |            |          |&lt;br /&gt;|*  9 |        DOMAIN INDEX               | CTX_BOOKS_TEXT |       |       |    12   (0)| 00:00:01 |&lt;br /&gt;----------------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   6 - access("AUTHOR"='XDB')&lt;br /&gt;   9 - access("CTXSYS"."CONTAINS"("TEXT",'procedure',1)&gt;0)&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         13  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;         57  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;       1454  bytes sent via SQL*Net to client&lt;br /&gt;        372  bytes received via SQL*Net from client&lt;br /&gt;          4  SQL*Net roundtrips to/from client&lt;br /&gt;          2  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;         37  rows processed&lt;/pre&gt;We are down to only 57 LIOs. However, the entire thing still requires us to fetch all relevant rowids from both indexes followed by a merge. On huge document sets each step may output quite a bit of data and require substantial processing resources.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;The new filter option in 11G&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;11G's Oracle Text allows you to specify structured data columns in a filter section while creating Oracle Text indexes:&lt;br /&gt;&lt;pre&gt;SQL&gt; CREATE INDEX ctx_books_text&lt;br /&gt;  2    ON books (text)&lt;br /&gt;  3    INDEXTYPE IS CTXSYS.CONTEXT&lt;br /&gt;  4    FILTER BY author&lt;br /&gt;  5    PARAMETERS('filter ctxsys.null_filter lexer books_lexer memory 64m');&lt;br /&gt;&lt;br /&gt;Index created.&lt;/pre&gt;Let's see how the search will look like now:&lt;br /&gt;&lt;pre&gt;SQL&gt; select author, title, published&lt;br /&gt;  2   from books&lt;br /&gt;  3   where contains(text, 'procedure') &gt; 0&lt;br /&gt;  4    and author='XDB';&lt;br /&gt;&lt;br /&gt;37 rows selected.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Execution Plan&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;Plan hash value: 2443340341&lt;br /&gt;&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;| Id  | Operation                   | Name           | Rows  | Bytes | Cost (%CPU)| Time     |&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;|   0 | SELECT STATEMENT            |                |    47 | 96679 |    11   (0)| 00:00:01 |&lt;br /&gt;|   1 |  TABLE ACCESS BY INDEX ROWID| BOOKS          |    47 | 96679 |    11   (0)| 00:00:01 |&lt;br /&gt;|*  2 |   DOMAIN INDEX              | CTX_BOOKS_TEXT |       |       |     8   (0)| 00:00:01 |&lt;br /&gt;----------------------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;Predicate Information (identified by operation id):&lt;br /&gt;---------------------------------------------------&lt;br /&gt;&lt;br /&gt;   2 - access("CTXSYS"."CONTAINS"("TEXT",'procedure')&gt;0)&lt;br /&gt;       filter("AUTHOR"='XDB')&lt;br /&gt;&lt;br /&gt;Note&lt;br /&gt;-----&lt;br /&gt;   - dynamic sampling used for this statement&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Statistics&lt;br /&gt;----------------------------------------------------------&lt;br /&gt;         13  recursive calls&lt;br /&gt;          0  db block gets&lt;br /&gt;         48  consistent gets&lt;br /&gt;          0  physical reads&lt;br /&gt;          0  redo size&lt;br /&gt;       1357  bytes sent via SQL*Net to client&lt;br /&gt;        372  bytes received via SQL*Net from client&lt;br /&gt;          4  SQL*Net roundtrips to/from client&lt;br /&gt;          0  sorts (memory)&lt;br /&gt;          0  sorts (disk)&lt;br /&gt;         37  rows processed&lt;/pre&gt;Note only 48 LIOs. While it may not be a solid improvement over merged indexes example, the important point here is that Oracle was able to get all required data from a context index alone.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;What about 10G?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There is no filter option for context indexes in 10G, however, that doesn't mean that there is nothing you could do to help you facilitate your mixed structured and unstructured data searches. I'll blog about what could be done in 10G next time which, by the way, can be applied to 11G as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-4151240674901248662?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2009/01/searching-over-structured-and.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-3469793034632507564</guid><pubDate>Wed, 31 Dec 2008 04:33:00 +0000</pubDate><atom:updated>2008-12-30T23:57:12.798-05:00</atom:updated><title>Alter database datafile offline drop</title><description>There was a topic on oracle-l recently and that remind me that offline drop provides one useful feature which people usually not leveraging.&lt;br /&gt;&lt;br /&gt;This feature not only allows you to open your database &lt;span style="font-style:italic;"&gt;without&lt;/span&gt; datafile foo...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;SQL&gt; startup&lt;br /&gt;ORACLE instance started.&lt;br /&gt;&lt;br /&gt;Total System Global Area  700448768 bytes&lt;br /&gt;Fixed Size                  1260844 bytes&lt;br /&gt;Variable Size             310379220 bytes&lt;br /&gt;Database Buffers          385875968 bytes&lt;br /&gt;Redo Buffers                2932736 bytes&lt;br /&gt;Database mounted.&lt;br /&gt;ORA-01157: cannot identify/lock data file 4 - see DBWR trace file&lt;br /&gt;ORA-01110: data file 4: '/u01/oradata/XE/datafile/foo.dbf'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;SQL&gt; alter database datafile 4 offline drop;&lt;br /&gt;&lt;br /&gt;Database altered.&lt;br /&gt;&lt;br /&gt;SQL&gt; alter database open;&lt;br /&gt;&lt;br /&gt;Database altered.&lt;/pre&gt;But what's really important about this is that offline drop doesn't &lt;span style="font-style:italic;"&gt;really&lt;/span&gt; drops anything (it just updates the controlfile to say that file isn't there) and what you can do later is:&lt;br /&gt;&lt;pre&gt;[oracle@srm oradata]$ rman&lt;br /&gt;&lt;br /&gt;Recovery Manager: Release 10.2.0.1.0 - Production on Tue Dec 30 19:12:22 2008&lt;br /&gt;&lt;br /&gt;Copyright (c) 1982, 2005, Oracle.  All rights reserved.&lt;br /&gt;&lt;br /&gt;RMAN&gt; connect target;&lt;br /&gt;&lt;br /&gt;connected to target database: XE (DBID=2555430687)&lt;br /&gt;&lt;br /&gt;RMAN&gt; restore tablespace foo;&lt;br /&gt;&lt;br /&gt;Starting restore at 30-DEC-08&lt;br /&gt;using target database control file instead of recovery catalog&lt;br /&gt;allocated channel: ORA_DISK_1&lt;br /&gt;channel ORA_DISK_1: sid=73 devtype=DISK&lt;br /&gt;&lt;br /&gt;channel ORA_DISK_1: starting datafile backupset restore&lt;br /&gt;channel ORA_DISK_1: specifying datafile(s) to restore from backup set&lt;br /&gt;restoring datafile 00004 to /u01/oradata/XE/datafile/foo.dbf&lt;br /&gt;channel ORA_DISK_1: reading from backup piece /u01/oradata/fra/XE/backupset/2008_12_30/o1_mf_nnnd0_TAG20081230T190854_4oog0pt0_.bkp&lt;br /&gt;channel ORA_DISK_1: restored backup piece 1&lt;br /&gt;piece handle=/u01/oradata/fra/XE/backupset/2008_12_30/o1_mf_nnnd0_TAG20081230T190854_4oog0pt0_.bkp tag=TAG20081230T190854&lt;br /&gt;channel ORA_DISK_1: restore complete, elapsed time: 00:00:02&lt;br /&gt;Finished restore at 30-DEC-08&lt;br /&gt;&lt;br /&gt;RMAN&gt; recover tablespace foo;&lt;br /&gt;&lt;br /&gt;Starting recover at 30-DEC-08&lt;br /&gt;using channel ORA_DISK_1&lt;br /&gt;&lt;br /&gt;starting media recovery&lt;br /&gt;media recovery complete, elapsed time: 00:00:01&lt;br /&gt;&lt;br /&gt;Finished recover at 30-DEC-08&lt;br /&gt;&lt;br /&gt;RMAN&gt; sql 'alter tablespace foo online';&lt;br /&gt;&lt;br /&gt;sql statement: alter tablespace foo online&lt;/pre&gt;In other words, it can be used to facilitate your database restore in certain cases. Imagine that your database has transactional and reporting data and that that data is spread across different tablespaces. In case your transactional data volume is small compared to reporting stuff (which is usually the case), you can plan your restore like this:&lt;br /&gt;&lt;ui&gt;&lt;br /&gt;&lt;li&gt;Restore everything but your analytical tablespaces, then offline drop missing datafiles and open your database =&gt; you are back into transactional business and immediate world stops screaming around you.&lt;br /&gt;&lt;li&gt;Restore your analytical tablespaces =&gt; they don't have to steer the company blindly anymore.&lt;br /&gt;&lt;/ui&gt;&lt;br /&gt;Depending on the data volumes, the first step might take only a fraction of time compared to restoring everything in one shot.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-3469793034632507564?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2008/12/alter-database-datafile-offline-drop.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">2</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8786030742306828328</guid><pubDate>Fri, 05 Dec 2008 12:18:00 +0000</pubDate><atom:updated>2008-12-11T14:00:43.857-05:00</atom:updated><title>Reanimation</title><description>There are changes.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;This blog goes English&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The goal here is to provide content to a broader audience.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;I'm no longer with Pythian&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now, that requires some confession. Here goes mine. &lt;span style="font-style:italic;"&gt;I was doing my job the right way.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here are some examples of the responses I'm still getting from my ex-clients:&lt;br /&gt;&lt;blockquote&gt;&lt;span style="font-style:italic;"&gt;We are sorry to see you go. You brought a higher level of expertise to our account than was previously being provided.&lt;br /&gt;&lt;br /&gt;Sorry to hear you're no longer with Pythian.&lt;br /&gt;&lt;br /&gt;Thanks for all your help Alex, I will miss you.&lt;br /&gt;&lt;br /&gt;Best of Luck for your future endeavors. Hope in the future I may a get a chance to meet  you personally.&lt;br /&gt;&lt;br /&gt;I wish you success in your future endeavors and you will be missed.&lt;br /&gt;&lt;br /&gt;Good luck with your new direction and it’s an honor working with you.&lt;/span&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;I'll explain what happened in the next post.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8786030742306828328?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2008/12/reanimation_05.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item><item><guid isPermaLink="false">tag:blogger.com,1999:blog-15440695.post-8702215120035559805</guid><pubDate>Fri, 04 Jan 2008 03:26:00 +0000</pubDate><atom:updated>2008-01-03T22:28:30.899-05:00</atom:updated><title>Настроение</title><description>&lt;a href="http://www.youtube.com/watch?v=1uwOL4rB-go&amp;amp;feature=related"&gt;&lt;span&gt;Achmed the Dead Terrorist&lt;/a&gt;. Я чуть не помер со смеху. Рекомендую.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:85%;"&gt;&lt;span style="font-style: italic;"&gt;P.S. Необходимо знание английского.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15440695-8702215120035559805?l=afatkulin.blogspot.com'/&gt;&lt;/div&gt;</description><link>http://afatkulin.blogspot.com/2008/01/blog-post.html</link><author>noreply@blogger.com (Alex Fatkulin)</author><thr:total xmlns:thr="http://purl.org/syndication/thread/1.0">0</thr:total></item></channel></rss>
