{
  "date": "2016-08-07T16:13:53",
  "slug": "multitenant-internals-object-links-on-fixed-tables",
  "link": "https://www.dbi-services.com/blog/multitenant-internals-object-links-on-fixed-tables/",
  "title": {
    "rendered": "Multitenant internals: object links on fixed tables"
  },
  "content": {
    "rendered": "<h2>By Franck Pachot</h2>\n<p>.<br />\nThe <a href=\"http://dbi-services.com/blog/multitenant-internals-how-object-links-are-parsedexecuted/\" target=\"_blank\" rel=\"noopener noreferrer\">previous post</a> partly answered to the original question (why an object link to V$SESSION is refreshed only every 30 seconds): recursive queries on shared=object views. Now let&#8217;s see what is different with fixed tables.<br />\n<!--more--><br />\nDisclaimer: this is research only. Don&#8217;t do that anywhere else than a lab. This is implicit when the title is &#8216;internals&#8217;.</p>\n<h3>Result cache</h3>\n<p>When query on a shared=object view is executed from a PDB, the session switches to the CDB to run a recursive query to get the rows. This query uses result cache by adding the following hint:</p>\n<pre><code>/*+ RESULT_CACHE (SYSOBJ=TRUE) */</code></pre>\n<p>This enables result cache for the rows fetched by this query, and even for system object. The &#8216;SYSOBJ=TRUE&#8217; is there because the &#8220;_rc_sys_obj_enabled&#8221; defaults to true.</p>\n<p>Here is the result cache from the previous post (I flushed the result cache just before the second run because in 12<em>c</em> hard parsing is also using a lot the result cache):</p>\n<pre><code>\n16:34:00 SQL&gt; select con_id,sql_id,rows_processed,plan_hash_value,executions,parse_calls,fetches,buffer_gets,sql_text from v$sql where  plan_hash_value in (3598352655,3551671056) order by last_active_time;\n&nbsp;\n    CON_ID SQL_ID        ROWS_PROCESSED PLAN_HASH_VALUE EXECUTIONS PARSE_CALLS    FETCHES BUFFER_GETS SQL_TEXT\n---------- ------------- -------------- --------------- ---------- ----------- ---------- ----------- --------------------------------------------------------------------------------\n         1 350gg6247sfa6            200      3598352655          2           2          2          26 SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE) */ ID,NUM FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\n         3 bynmh7xm4bf54              0      3598352655          0           5          0          51 SELECT * FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\")\n         3 duy45bn72jr35            200      3551671056          2           2         16         269 select id from DEMOV where num column name format a120 trunc\n16:34:00 SQL&gt; select type,status,name,row_count from v$result_cache_objects order by row_count desc fetch first 10 rows only;\n&nbsp;\nTYPE       STATUS    NAME                                                                                                                      ROW_COUNT\n---------- --------- ------------------------------------------------------------------------------------------------------------------------ ----------\nResult     Published SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE) */ ID,NUM FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\") \"DEMOV\" WHERE \"DEMOV\".\"NUM\"&lt;=100             100\nDependency Published SYS.DEMOT                                                                                                                         0\nDependency Published SYS.DEMOV                                                                                                                         0\n</code></pre>\n<p>As with regular result cache, there is dependency tracking: as soon as the underlying table has some modification, the cache will be invalidated. So this query is always guaranteed to get fresh results.</p>\n<h3>Invalidation</h3>\n<p>I did the same when deleting half of the rows before the second execution in order to invalidate the result cache:</p>\n<pre><code>\n16:43:46 SQL&gt; select con_id,sql_id,rows_processed,plan_hash_value,executions,parse_calls,fetches,buffer_gets,sql_text from v$sql where  plan_hash_value in (3598352655,3551671056) order by last_active_time;\n&nbsp;\n    CON_ID SQL_ID        ROWS_PROCESSED PLAN_HASH_VALUE EXECUTIONS PARSE_CALLS    FETCHES BUFFER_GETS SQL_TEXT\n---------- ------------- -------------- --------------- ---------- ----------- ---------- ----------- --------------------------------------------------------------------------------\n         1 350gg6247sfa6            150      3598352655          2           2          2          26 SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE) */ ID,NUM FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\n         3 bynmh7xm4bf54              0      3598352655          0           5          0          51 SELECT * FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\")\n         3 duy45bn72jr35            150      3551671056          2           2         13         269 select id from DEMOV where num column name format a120 trunc\n16:43:46 SQL&gt; select type,status,name,row_count from v$result_cache_objects order by row_count desc fetch first 10 rows only;\n&nbsp;\nTYPE       STATUS    NAME                                                                                                                      ROW_COUNT\n---------- --------- ------------------------------------------------------------------------------------------------------------------------ ----------\nResult     Invalid   SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE) */ ID,NUM FROM NO_OBJECT_LINK(\"SYS\".\"DEMOV\") \"DEMOV\" WHERE \"DEMOV\".\"NUM\"&lt;=100             100\nResult     Published SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE) */ ID,NUM FROM NO_OBJECT_LINK(&quot;SYS&quot;.&quot;DEMOV&quot;) &quot;DEMOV&quot; WHERE &quot;DEMOV&quot;.&quot;NUM&quot;&lt;=100              50\nDependency Published SYS.DEMOT                                                                                                                         0\nDependency Published SYS.DEMOV                                                                                                                         0\n</code></pre>\n<p>I&#8217;ve 100 rows from the first run, invalidated, and them 50 rows from the second one.</p>\n<p>Note that I&#8217;ve the same result when I set &#8220;_disable_cdb_view_rc_invalidation&#8221;=true. Sometimes undocumented parameters behavior cannot be guessed only from their name.</p>\n<h3>Fixed tables</h3>\n<p>I&#8217;ve run the same testcase but with the following definition of DEMOV:</p>\n<pre><code>\ncreate view DEMOV sharing=object as select saddr id, rownum num from V$SESSION;\n</code></pre>\n<p>Here is the trace of the recursive query run in CDB$ROOT, at first execution:</p>\n<pre><code>PARSING IN CURSOR #140436732146672 len=112 dep=1 uid=0 oct=3 lid=0 tim=769208810641 hv=3298783355 ad='108ee92f0' sqlid=' 10qf9kb29yw3v'\nSELECT /*+ RESULT_CACHE (SYSOBJ=TRUE SHELFLIFE=30) */ ID,NUM FROM \"SYS\".\"DEMOV\" \"DEMOV\" WHERE \"DEMOV\".\"NUM\"&lt;=100\nEND OF STMT\nPARSE #140436732146672:c=4000,e=10614,p=0,cr=6,cu=0,mis=1,r=0,dep=1,og=4,plh=350654732,tim=769208810640\nEXEC #140436732146672:c=0,e=17,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,plh=350654732,tim=769208810701\nFETCH #140436732146672:c=1000,e=961,p=0,cr=0,cu=0,mis=0,r=53,dep=1,og=4,plh=350654732,tim=769208811687\nSTAT #140436732146672 id=1 cnt=53 pid=0 pos=1 obj=0 op=&#039;RESULT CACHE  byq3fbkawmkm34gtfk1csvwv52 (cr=0 pr=0 pw=0 time=877 us)&#039;\nSTAT #140436732146672 id=2 cnt=53 pid=1 pos=1 obj=98258 op=&#039;VIEW  DEMOV (cr=0 pr=0 pw=0 time=655 us cost=0 size=171 card=9)&#039;\nSTAT #140436732146672 id=3 cnt=53 pid=2 pos=1 obj=0 op=&#039;COUNT  (cr=0 pr=0 pw=0 time=652 us)&#039;\nSTAT #140436732146672 id=4 cnt=53 pid=3 pos=1 obj=0 op=&#039;NESTED LOOPS  (cr=0 pr=0 pw=0 time=597 us cost=0 size=306 card=9)&#039;\nSTAT #140436732146672 id=5 cnt=53 pid=4 pos=1 obj=0 op=&#039;NESTED LOOPS  (cr=0 pr=0 pw=0 time=436 us cost=0 size=270 card=9)&#039;\nSTAT #140436732146672 id=6 cnt=53 pid=5 pos=1 obj=0 op=&#039;FIXED TABLE FULL X$KSLWT (cr=0 pr=0 pw=0 time=219 us cost=0 size=352 card=44)&#039;\nSTAT #140436732146672 id=7 cnt=53 pid=5 pos=2 obj=0 op=&#039;FIXED TABLE FIXED INDEX X$KSUSE (ind:1) (cr=0 pr=0 pw=0 time=101 us cost=0 size=22 card=1)&#039;\nSTAT #140436732146672 id=8 cnt=53 pid=4 pos=2 obj=0 op=&#039;FIXED TABLE FIXED INDEX X$KSLED (ind:2) (cr=0 pr=0 pw=0 time=70 us cost=0 size=4 card=1)&#039;</code></pre>\n<p>The difference here is that SHELFLIFE=30 has been added to the generated result cache hint.</p>\n<p>The second run has very short parse time (c=0) because it&#8217;s a soft parse but you also see very short fetch time (c=0) because it&#8217;s a cache hit:</p>\n<pre><code>PARSING IN CURSOR #140436733602136 len=112 dep=1 uid=0 oct=3 lid=0 tim=769208821904 hv=3298783355 ad='108ee92f0' sqlid=' 10qf9kb29yw3v'\nSELECT /*+ RESULT_CACHE (SYSOBJ=TRUE SHELFLIFE=30) */ ID,NUM FROM \"SYS\".\"DEMOV\" \"DEMOV\" WHERE \"DEMOV\".\"NUM\"&lt;=100\nEND OF STMT\nPARSE #140436733602136:c=0,e=29,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,plh=350654732,tim=769208821904\nEXEC #140436733602136:c=0,e=13,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,plh=350654732,tim=769208821955\nFETCH #140436733602136:c=0,e=18,p=0,cr=0,cu=0,mis=0,r=53,dep=1,og=4,plh=350654732,tim=769208821990</code></pre>\n<p>When I look at the result cache, there were no invalidations:</p>\n<pre><code>TYPE       STATUS    NAME                                                                                                                      ROW_COUNT\n---------- --------- ------------------------------------------------------------------------------------------------------------------------ ----------\nResult     Published SELECT /*+ RESULT_CACHE (SYSOBJ=TRUE SHELFLIFE=30) */ ID,NUM FROM \"SYS\".\"DEMOV\" \"DEMOV\" WHERE \"DEMOV\".\"NUM\"&lt;=100                 55\nDependency Published SYS.DEMOV                                                                                                                         0</code></pre>\n<p>When SHELFLIFE is set in a result cache hint, there is no dependency tracking. I&#8217;ve described <a href=\"http://dbi-services.com/blog/resultcache-hint-expiration-options/\" target=\"_blank\" rel=\"noopener noreferrer\">RESULT_CACHE hint expiration options</a> in a previous post.</p>\n<p>The V$ views are on fixed tables, structures in memory, where there is no dependency tracking possibility. This is probably why the recursive query for sharing=object views use a SHELFLIFE instead.</p>\n<p>This means that if you create an object link view on a fixed table the query will show same result for the next executions for 30 seconds.</p>\n<h3>&#8220;_cdb_view_rc_shelflife&#8221;</h3>\n<p>I&#8217;ve tested a shared=object view on V$SESSION as an <a href=\"http://dbi-services.com/blog/oracle-12c-cdb-metadata-a-object-links-internals/#comment-4179\" target=\"_blank\" rel=\"noopener noreferrer\">answer to a previous blog comment</a>. My query selects MAX(LAST_ET_CALL) which is supposed to increase every second for the inactive sessions. And we see that the result changes only every 30 seconds.</p>\n<p>Those 30 seconds are parametered by &#8220;_cdb_view_rc_shelflife&#8221;. Here is the same test where I set &#8220;_cdb_view_rc_shelflife&#8221; to 5 seconds:</p>\n<pre><code>\n15:31:48 SQL&gt; alter session set \"_cdb_view_rc_shelflife\"=5;\nSession altered.\n&nbsp;\n15:31:48 SQL&gt; set serveroutput on\n15:31:48 SQL&gt; declare\n15:31:48   2   x varchar2(100);\n15:31:48   3  begin\n15:31:48   4   for i in 1..60 loop\n15:31:48   5    dbms_lock.sleep(1);\n15:31:48   6    select to_char(current_timestamp)||' --&gt; '||max(last_call_et) into x from DEMOV;\n15:31:48   7    dbms_output.put_line(x);\n15:31:48   8   end loop;\n15:31:48   9  end;\n15:31:48  10  /\n&nbsp;\n07-AUG-16 03.31.49.852081 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.50.863742 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.51.863753 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.52.864697 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.53.864706 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.54.864726 PM +00:00 --&gt; 775144\n07-AUG-16 03.31.55.864669 PM +00:00 --&gt; 775150\n07-AUG-16 03.31.56.864711 PM +00:00 --&gt; 775150\n07-AUG-16 03.31.57.864754 PM +00:00 --&gt; 775150\n07-AUG-16 03.31.58.864702 PM +00:00 --&gt; 775150\n07-AUG-16 03.31.59.864711 PM +00:00 --&gt; 775150\n07-AUG-16 03.32.00.864779 PM +00:00 --&gt; 775150\n07-AUG-16 03.32.01.865710 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.02.866738 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.03.866719 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.04.866787 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.05.866758 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.06.866805 PM +00:00 --&gt; 775156\n07-AUG-16 03.32.07.867738 PM +00:00 --&gt; 775162\n07-AUG-16 03.32.08.868743 PM +00:00 --&gt; 775162\n07-AUG-16 03.32.09.868727 PM +00:00 --&gt; 775162\n07-AUG-16 03.32.10.868724 PM +00:00 --&gt; 775162\n07-AUG-16 03.32.11.868758 PM +00:00 --&gt; 775162\n07-AUG-16 03.32.12.869763 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.13.870741 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.14.870742 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.15.870721 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.16.870734 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.17.870883 PM +00:00 --&gt; 775167\n07-AUG-16 03.32.18.872741 PM +00:00 --&gt; 775173\n07-AUG-16 03.32.19.873837 PM +00:00 --&gt; 775173\n</code></pre>\n<p>And here is the same test after setting:</p>\n<pre><code>\nSQL&gt; exec dbms_result_cache.bypass(true);\n</code></pre>\n<p>I&#8217;ve not tested, but I expect the same in Standard Edition where result cache is disabled</p>\n<pre><code>\n07-AUG-16 03.43.32.158741 PM +00:00 --&gt; 775846\n07-AUG-16 03.43.33.185793 PM +00:00 --&gt; 775847\n07-AUG-16 03.43.34.186633 PM +00:00 --&gt; 775848\n07-AUG-16 03.43.35.186738 PM +00:00 --&gt; 775849\n07-AUG-16 03.43.36.187696 PM +00:00 --&gt; 775850\n07-AUG-16 03.43.37.188684 PM +00:00 --&gt; 775851\n07-AUG-16 03.43.38.188692 PM +00:00 --&gt; 775852\n07-AUG-16 03.43.39.189755 PM +00:00 --&gt; 775853\n07-AUG-16 03.43.40.190697 PM +00:00 --&gt; 775854\n07-AUG-16 03.43.41.191763 PM +00:00 --&gt; 775855\n07-AUG-16 03.43.42.192706 PM +00:00 --&gt; 775856\n07-AUG-16 03.43.43.193736 PM +00:00 --&gt; 775857\n</code></pre>\n<h3>Conclusion</h3>\n<p>Don&#8217;t be afraid. There are very few sharing=object views in the dictionary, and only few of them have dependencies on fixed tables:</p>\n<pre><code>\nSQL&gt; select owner,name,referenced_name from dba_dependencies\n     where (referenced_owner,referenced_name) in (select 'SYS',view_name from v$fixed_view_definition union select 'SYS',name from v$fixed_table)\n     and (owner,name,type) in (select owner,object_name,object_type from dba_objects where sharing='OBJECT LINK')\n     ;\n&nbsp;\nOWNER                          NAME                           REFERENCED_NAME\n------------------------------ ------------------------------ ------------------------------\nSYS                            INT$DBA_HIST_SQLSTAT           X$MODACT_LENGTH\nSYS                            INT$DBA_HIST_ACT_SESS_HISTORY  X$MODACT_LENGTH\nSYS                            INT$DBA_OUTSTANDING_ALERTS     X$KELTGSD\nSYS                            INT$DBA_OUTSTANDING_ALERTS     X$KELTSD\nSYS                            INT$DBA_OUTSTANDING_ALERTS     X$KELTOSD\nSYS                            INT$DBA_ALERT_HISTORY          X$KELTGSD\nSYS                            INT$DBA_ALERT_HISTORY          X$KELTSD\nSYS                            INT$DBA_ALERT_HISTORY          X$KELTOSD\nSYS                            INT$DBA_ALERT_HISTORY_DETAIL   X$KELTGSD\nSYS                            INT$DBA_ALERT_HISTORY_DETAIL   X$KELTSD\nSYS                            INT$DBA_ALERT_HISTORY_DETAIL   X$KELTOSD\nSYS                            DEMOV                          GV$SESSION\n&nbsp;\n6 rows selected.\n</code></pre>\n<p>I&#8217;ve described how AWR views are stacked onto each other in a <a href=\"http://dbi-services.com/blog/12c-multitenant-internals-awr-tables-and-views/\" target=\"_blank\" rel=\"noopener noreferrer\">previous post</a>.</p>\n<p>And don&#8217;t worry, you don&#8217;t need to have a fresh view of those X$ tables. As an example, behind DBA_HIST_ACTIVE_SES_HISTORY the fixed table X$MODACT_LENGTH holds only the length of module and action strings:</p>\n<pre><code>SQL&gt; select * from X$MODACT_LENGTH;\n&nbsp;\nADDR                   INDX    INST_ID     CON_ID  KSUMODLEN  KSUACTLEN\n---------------- ---------- ---------- ---------- ---------- ----------\n00007FF2EF280920          0          1          0         48         32</code></pre>\n<p>And the others (X$KELTSD, X$KELTGSD, X$KELTOSD) are the structures behind V$ALERT_TYPES that are not supposed to change.</p>\n<p>So don&#8217;t panic. The multitenant architecture has some strange implementation stuff, but mostly harmless&#8230;</p>\n",
    "protected": false
  }
}
