Mastering XML DB – Unexpected side effect of updateXML

0

When trying to create anonymous data, I encountered an unexpected side effect of the updateXML statement while updating XML documents in an Oracle 10gR2 (10.2.0.1.0) EE database.

I got a small test environment in which a table (HGO010_DETAM) resides that contains approximately 500Mb of XML documents. The real production environment is much bigger, think in 100th of Gigabytes per table and an average of 512 Kb (up to 6 Mb) per document. The web application that makes use of this data is document driven so these documents were stored in an XMLType column based on CLOB storage. The table also contains an extra ID column that was added for reference purposes.

The table was created as follows:....

SQL&gt; create table HGO010_DETAM<br />&nbsp; 2&nbsp; (HGO_ID NUMBER(12), GEGEVENS XMLTYPE)<br />&nbsp; 3&nbsp; ;

The full syntax would have been as shown via the dbms_metadata method:

SQL&gt; select dbms_metadata.get_ddl('TABLE','HGO010_DETAM',USER) from dual;<br />  DBMS_METADATA.GET_DDL('TABLE','HGO010_DETAM','HGO')<br />--------------------------------------------------------------------------------&nbsp; CREATE TABLE &quot;HGO&quot;.&quot;HGO010_DETAM&quot;<br />&nbsp;&nbsp; (&nbsp;&nbsp;&nbsp; &quot;HGO_ID&quot; NUMBER(12,0) NOT NULL ENABLE,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;GEGEVENS&quot; &quot;SYS&quot;.&quot;XMLTYPE&quot;&nbsp; NOT NULL ENABLE,<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CONSTRAINT &quot;FK_HGO010_HGO000&quot; FOREIGN KEY (&quot;HGO_ID&quot;)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; REFERENCES &quot;HGO&quot;.&quot;HGO000_SOFI&quot; (&quot;HGO_ID&quot;) ON DELETE CASCADE ENABLE<br />&nbsp;&nbsp; ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING<br />&nbsp; STORAGE(INITIAL 2097152 NEXT 2097152 MINEXTENTS 1 MAXEXTENTS 2147483645<br />&nbsp; PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)<br />&nbsp; TABLESPACE &quot;DAT_TS01&quot;<br />&nbsp;XMLTYPE COLUMN &quot;GEGEVENS&quot; STORE AS CLOB (<br />&nbsp; TABLESPACE &quot;DAT_TS01&quot; ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10<br />&nbsp; NOCACHE LOGGING<br />&nbsp; STORAGE(INITIAL 2097152 NEXT 2097152 MINEXTENTS 1 MAXEXTENTS 2147483645<br />&nbsp; PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT))

A typical document in this table looks like this:

SQL&gt; desc hgo.hgo010_detam<br />&nbsp;Name&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Null?&nbsp;&nbsp;&nbsp; Type<br />&nbsp;----------------------------------------- -------- ----------------------------<br />&nbsp;HGO_ID&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NOT NULL NUMBER(12)<br />&nbsp;GEGEVENS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NOT NULL SYS.XMLTYPE
<br />SQL&gt; select&nbsp; *<br />&nbsp; 2&nbsp; from&nbsp;&nbsp;&nbsp; hgo.hgo010_detam t<br />&nbsp; 3&nbsp; where&nbsp;&nbsp; t.hgo_id=18383;<br />
&nbsp;&nbsp;&nbsp; HGO_ID<br />----------<br />T.GEGEVENS.EXTRACT('/*')<br />--------------------------------------------------------------------------------<br />&nbsp;&nbsp;&nbsp;&nbsp; 18383<br />&lt;wwb:WW-HISTORIE xmlns:xsi=&quot;<a href="http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance</a>&quot; xmlns:wwb<br />=&quot;<a href="http://www.uwv.nl/ww/historie/detam/WWBase">http://www.uwv.nl/ww/historie/detam/WWBase</a>&quot; xmlns:wwe=&quot;<a href="http://www.uwv.nl/ww/hi">http://www.uwv.nl/ww/hi</a><br />storie/detam/WWEntiteiten&quot;&gt;<br />&nbsp; &lt;R801FINR&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-NUM-FIS&gt;012345678&lt;/R801-NUM-FIS&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-NUM-DET&gt;426270670105&lt;/R801-NUM-DET&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-IND-MUT-NUM-DET/&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R802GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R802-NUM-GVL-WW&gt;94628838&lt;/R802-NUM-GVL-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R806GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R806-NUM-GVL-WW&gt;94628838&lt;/R806-NUM-GVL-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R805LIDNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R805-NUM-LID&gt;09045451&lt;/R805-NUM-LID&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/R805LIDNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/R806GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;OUDWWREC&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815GEVFIL1&gt;0&lt;/R815GEVFIL1&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815GEVALSNR&gt;94628838&lt;/R815GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815-NUM-FIS&gt;220111765&lt;/R815-NUM-FIS&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815LIDNR&gt;09045451&lt;/R815LIDNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815INDWW&gt;4&lt;/R815INDWW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815-CAT-WW&gt;04&lt;/R815-CAT-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815-DAT-BGN-WW&gt;19950703&lt;/R815-DAT-BGN-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R815-DAT-END-WW&gt;19950726&lt;/R815-DAT-END-WW&gt;

So as you can see, the document contains white spaces and has a "pretty print" layout. This is how the data was loaded in the XMLType column. The XML documents held there original layout because the XMLType column has a CLOB based storage (and therefore the data isn’t shredded as in XMLType columns based on Object Relational storage). XMLType CLOB storage stores unstructured data "as is".

I needed to create anonymous data, for instance regarding the <R801-NUM-FIS> element. So tried an update of the data via the statement:

SQL&gt; update hgo.hgo010_detam t<br />     set&nbsp;&nbsp;&nbsp; t.gegevens=updateXML<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(t.gegevens,'/wwb:WW-HISTORIE/R801FINR/R801-NUM-FIS/text()'<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ,'Marco','xmlns:wwb=&quot;<a href="http://www.uwv.nl/ww/historie/detam/WWBase&quot;'">http://www.uwv.nl/ww/historie/detam/WWBase&quot;'</a>)<br />     where&nbsp; hgo_id=18383;<br />&nbsp;<br />1 row updated.

The update statement worked fine but I was surprised when I re-examined the data and saw the following:

SQL&gt; select&nbsp; *<br />&nbsp; 2&nbsp; from&nbsp;&nbsp;&nbsp; hgo.hgo010_detam t<br />&nbsp; 3&nbsp; where&nbsp;&nbsp; t.hgo_id=18383;<br />&nbsp;<br />&nbsp;&nbsp;&nbsp; HGO_ID<br />----------<br />GEGEVENS<br />--------------------------------------------------------------------------------<br />&nbsp;&nbsp;&nbsp;&nbsp; 18383<br />&lt;?xml version=&quot;1.0&quot; encoding=&quot;ISO-8859-1&quot;?&gt;&lt;wwb:WW-HISTORIE xmlns:xsi=&quot;<a href="http://ww/">http://ww</a><br />w.w3.org/2001/XMLSchema-instance&quot; xmlns:wwb=&quot;<a href="http://www.uwv.nl/ww/historie/detam">http://www.uwv.nl/ww/historie/detam</a><br />/WWBase&quot; xmlns:wwe=&quot;<a href="http://www.uwv.nl/ww/historie/detam/WWEntiteiten&quot;&gt;&lt;R801FINR">http://www.uwv.nl/ww/historie/detam/WWEntiteiten&quot;&gt;&lt;R801FINR</a>&gt;<br />&lt;R801-NUM-FIS&gt;Marco&lt;/R801-NUM-FIS&gt;&lt;R801-NUM-DET&gt;426270670105&lt;/R801-NUM-DET&gt;&lt;R801<br />-IND-MUT-NUM-DET/&gt;&lt;R802GEVALSNR&gt;&lt;R802-NUM-GVL-WW&gt;94628838&lt;/R802-NUM-GVL-WW&gt;&lt;R806<br />GEVALSNR&gt;&lt;R806-NUM-GVL-WW&gt;94628838&lt;/R806-NUM-GVL-WW&gt;&lt;R805LIDNR&gt;&lt;R805-NUM-LID&gt;090<br />45451&lt;/R805-NUM-LID&gt;&lt;/R805LIDNR&gt;&lt;/R806GEVALSNR&gt;&lt;OUDWWREC&gt;&lt;R815GEVFIL1&gt;0&lt;/R815GEV<br />FIL1&gt;&lt;R815GEVALSNR&gt;94628838&lt;/R815GEVALSNR&gt;&lt;R815-NUM-FIS&gt;220111765&lt;/R815-NUM-FIS&gt;<br />...etc.etc.etc

Pretty print layout and all the white spaces where gone…

I mentioned the effect on the Oracle TechNet XMLDB forum (http://forums.oracle.com/forums/forum.jspa?forumID=34) and Mark Drake guessed that the following was probably happening under the covers (http://forums.oracle.com/forums/thread.jspa?threadID=399742&tstart=0):

"We had to parse the XML into a DOM to perform the update, perform the update using the DOM API methods and then reserialize the DOM into text after the update was complete. We only preserve whitespace during parsing when the XML is schema based and the element is defined as mixed="true" in the XML Schema, or in the case xml:space="preserve" (Both of these cases require patches to work as expected). So the whitespace (pretty print) was lost when the XML was parsed prior to performing the update."

"Since the XML is stored as CLOB we do not reparse it to pretty print when the row is selected.."

"You can force a pretty print by selecting"

"select HGO_ID, t.GEGEVENS.extract(‘/*’) from hgo010_detam t"

This sounds very plausible, testing the extract statement the following is shown in SQL*Plus:

SQL&gt; select HGO_ID, t.GEGEVENS.extract('/*') from hgo010_detam t<br />&nbsp; 2&nbsp; where t.hgo_id=18383;<br />&nbsp;<br />&nbsp;&nbsp;&nbsp; HGO_ID<br />----------<br />T.GEGEVENS.EXTRACT('/*')<br />--------------------------------------------------------------------------------<br />&nbsp;&nbsp;&nbsp;&nbsp; 18383<br />&lt;wwb:WW-HISTORIE xmlns:xsi=&quot;<a href="http://www.w3.org/2001/XMLSchema-instance">http://www.w3.org/2001/XMLSchema-instance</a>&quot; xmlns:wwb<br />=&quot;<a href="http://www.uwv.nl/ww/historie/detam/WWBase">http://www.uwv.nl/ww/historie/detam/WWBase</a>&quot; xmlns:wwe=&quot;<a href="http://www.uwv.nl/ww/hi">http://www.uwv.nl/ww/hi</a><br />storie/detam/WWEntiteiten&quot;&gt;<br />&nbsp; &lt;R801FINR&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-NUM-FIS&gt;Marco&lt;/R801-NUM-FIS&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-NUM-DET&gt;426270670105&lt;/R801-NUM-DET&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R801-IND-MUT-NUM-DET/&gt;<br />&nbsp;&nbsp;&nbsp; &lt;R802GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R802-NUM-GVL-WW&gt;94628838&lt;/R802-NUM-GVL-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R806GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R806-NUM-GVL-WW&gt;94628838&lt;/R806-NUM-GVL-WW&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R805LIDNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;R805-NUM-LID&gt;09045451&lt;/R805-NUM-LID&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/R805LIDNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;/R806GEVALSNR&gt;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;OUDWWREC&gt;<br />...etc..etc..etc

In my small environment the referenced table contains 7500 documents. The original total size of these documents on disks is 437.796 Kb (when shown in Windows explorer). When checking on the sizes of these (re-rendered) text documents in the mentioned table, the sum total size is 450136538 bytes. The smallest XML document in this table is 1584 bytes and the biggest XML document in this table is 1138638 bytes.

SQL&gt; select min(dbms_lob.getlength(t.gegevens.getclobval())) &quot;MIN&quot;<br />&nbsp; 2&nbsp; from hgo.hgo010_detam t<br />&nbsp; 3&nbsp; ;
MIN<br />---------<br />1584
1 row selected.
SQL&gt; select max(dbms_lob.getlength(t.gegevens.getclobval())) &quot;MAX&quot;<br />&nbsp; 2&nbsp; from hgo.hgo010_detam t<br />&nbsp; 3&nbsp; ;
MAX<br />---------<br />1138638
1 row selected.
SQL&gt; select sum(dbms_lob.getlength(t.gegevens.getclobval())) &quot;SUM&quot;<br />&nbsp; 2&nbsp; from hgo.hgo010_detam t<br />&nbsp; 3&nbsp; ;
SUM<br />---------<br />450136538
1 row selected.

After updating all XML documents via an updateXML statement and as a side effect of this, removing all white spaces:

<br />SQL&gt; update hgo.hgo010_detam t<br />&nbsp;&nbsp;&nbsp;&nbsp; set t.gegevens=updateXML<br />&nbsp;&nbsp;&nbsp;&nbsp; (t.gegevens,'/wwb:WW-HISTORIE/R801FINR/R801-NUM-FIS/text()'<br />&nbsp;&nbsp;&nbsp;&nbsp; ,'012345678','xmlns:wwb=&quot;<a href="http://www.uwv.nl/ww/historie/detam/WWBase&quot;'">http://www.uwv.nl/ww/historie/detam/WWBase&quot;'</a>);
7500 rows updated.

After this update the same SUM statement as shown above gave the following result:

SQL&gt; select sum(dbms_lob.getlength(t.gegevens.getclobval())) &quot;SUM&quot;<br />&nbsp; 2&nbsp; from hgo.hgo010_detam t<br />&nbsp; 3&nbsp; ;
SUM<br />---------<br />392869215
1 row selected.

This is 57267323 bytes less (~ 55 Mb).

SQL&gt; select 450136538-392869215 &quot;LESS&quot; from dual;
LESS<br />---------<br />57267323
1 row selected.

In conclusion – the updateXML statements has  (at least in our case) nice side effects; it removes the white spaces which causes less space consumption and will allow XML parsers to be more effective while parsing the XML documents. Keep in mind that, if you address DOM methods, this will be very memory intensive (your document will be rebuild in memory).

Share.

About Author

Marco Gralike, working for AMIS Services BV as a Principal Oracle Database Consultant in the Netherlands, has experience as a DBA since 1994 (Oracle 6). Marco is also eager and skillful in other fields, like Operating System Administration and Application Servers, mainly to find working, performing solutions. Marco has been specializing in Oracle XMLDB, since 2003, focusing on his old love, database administration and performance. He is an Oracle XMLDB enthusiast ever since. He is also a dedicated contributor of the Oracle User Group community, helping people with their steep XMLDB learning curve. To this purpose, Marco also devoted his personal blog site to XMLDB and other Oracle issues. Marco is a member of the OakTable network and an Oracle ACE Director (specialization Oracle XMLDB).

Comments are closed.