Skip to Main Content
  • Questions
  • Inserting clob performance issue: enable vs disable strorage in row

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Yves.

Asked: February 21, 2002 - 9:14 am UTC

Last updated: July 19, 2011 - 7:57 am UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Storage in row DISABLE vs ENABLE: why such a performance difference ?
With ENABLE, inserts is about 30 times faster than with DISABLE !

I thought using DISABLE would be a better option because the clob values (text to be filled) would be probably updated with a larger size and disable option would make easier storage management.

Would you suggest other option ?

Thank you.

drop table toto;
create table toto (
A VARCHAR2 (30) NOT NULL,
B VARCHAR2 (30) NOT NULL,
C clob default empty_clob()
)
lob(c) store as toto_name_lo(disable storage in row)
;

set feedback off
set timing on;
prompt
prompt Insert lob with enabled storage in row:
insert into toto (a, b, c)
select owner a, object_name b, owner || ' ' || object_name c from all_objects
where rownum < 1000
;
set timing off;
rollback;


drop table toto;
create table toto (
A VARCHAR2 (30) NOT NULL,
B VARCHAR2 (30) NOT NULL,
C clob default empty_clob()
)
lob(c) store as toto_name_lo(enable storage in row)
;
set timing on;
prompt
prompt Insert lob with disabled storage in row:
insert into toto (a, b, c)
select owner a, object_name b, owner || ' ' || object_name c from all_objects
where rownum < 1000
;
set timing off;
rollback;

-- This produces:

Connecté à :
Oracle8i Release 8.1.7.1.0 - Production
JServer Release 8.1.7.1.0 - Production


Table supprimée.


Table créée.


Insert lob with enabled storage in row:
Ecoulé : 00 :00 :15.59

Insert lob with disabled storage in row:
Ecoulé : 00 :00 :00.62
SQL>

and Tom said...

you are storing a trivial amount of data in the clob.

A clob stored out of line requires three structures:

o the table itself will have a lob locator (the size of this lob locator is actually LARGER then you test data above!)

o a lob index. The lob locator points into this index, this index then points to the pages (chunks) of the lob

o a lob segment. This is where the data finally goes to.


A clob stored inline requires 1 structure -- the table itself.

Storing clob data out of line (which will happen automatically once the clob exceeds about 4,000 bytes) makes it so that the structured table data is more efficiently stored and accessed. Consider a table with 100 rows and each row has a 4meg LOB. If the LOB were stored "inline", that would be a 400meg table -- full scanning that table would take 400meg of IO. Now, consider that the lob will be stored OUT of line -- a full scan of that table might read a single block (very few blocks would be needed to store 100 rows of data normally).

Do the test with REAL sized data. If most of your clobs are less then 4000 bytes, enabling storage inline makes sense (cuts out work). If most are greater then 4000 disabling inline storage might make sense.




Rating

  (44 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

But why such a big difference in response time ?

Yves Debizet, February 22, 2002 - 3:35 am UTC

Length of text to be filled will be between 0 and some hundreds of (UTF8) characters (rarely more than 4000 characters).

Due to the facts that, in the first phase of our project, we have a lot of insertion and slow response time when OUT of LINE storage, I will privilege IN LINE storage.

I will examine the possibility of OUT OF LINE in a later phase of the project.

What you answer does not tell about is why such a big difference between IN LINE and OUT OF LINE storage of clob ?

The reason might be that instead of inserting in one data block when IN LINE storage, three data blocks (data + index + lob blmocks) are inserted when OUT OF LINE storage.

But when inserting in a table with 2 indexes (so 1 data block + 2 index blocks) versus inserting in a table with no index, I do not expect such a big difference in time insertion !

Thank you for your answer.

Tom Kyte
February 22, 2002 - 11:03 am UTC

I don't see whats so hard to see here.

When we store data inline, its very very straight forward. If you have less then say 4000 bytes -- just put it there, done, return to client.

The code to do the out of line storage has a MUCH longer code path. Much much longer. It does a ton more work. It does more IO, it creates more data, it executes alot of code. Heck, in your example -- the lob locator you make us generate is LARGER in fact then the data itself. You are doing many TIMES the IO (the work) using this approach (out of line storage).

enable In row and chain count , Huge table distinct on a LOB table

A reader, September 20, 2002 - 5:47 pm UTC

I have this situation:
Unix , Oracle 8.1.7.4
Block size is 4k.
Chunk size is 4k
Enable storage in row for LOB.
Locally managed uniform tablespace both for table and LOB
Seperate tablespaces for LOB,table.
Tablespace1 extent size 4Mb
TablespaceLOB extentsize 128M
The table was initially allocated some space.(also initial was 
specified for the lob column storage clause).

Table1 has 35 columns out of which one is a blob  column(table1blob).

SQL>Analyze table1 compute statistics;

SQL>select segment_name "Table Name" ,tablespace_name "Table space" ,
sum(bytes)/1024 "Allocated Kb" ,  sum(bytes)/1024/1024 "Allocated Mb",
sum(blocks) "Allocated Blocks" from user_extents where 
segment_name='TABLE1' group by segment_name,tablespace_name ;

Table Name    Table space   Allocated Kb   Allocated Mb    Alloc Blocks
------------- ------------  ----------     ------------    ----------
TABLE1        TABLESPACE1   258048         252             64512


SQL>select segment_name from user_lobs where table_name='TABLE1';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

SQL>select segment_name "Table Name" ,tablespace_name "Table space" 
,sum(bytes)/1024 "Allocated Kb" ,  sum(bytes)/1024/1024 "Allocated Mb"
,sum(blocks) "Allocated Blocks" from user_extents where segment_name=
'SYS_LOB0000007813C00015$$' group by segment_name,tablespace_name ;

Table Name                 Table space      AllocKb   AllocMb  AllocBlks
-------------------------- ------------------------------ ------------ 
SYS_LOB0000007813C00015$$  TABLESPACE_LOB   1572864   1536      393216



SQL>select Table_name "Table Name" ,tablespace_name "Tablespace name" ,
avg_space "Avg free space", blocks "Blocks used" , 
empty_blocks "Empty blocks" , 
((blocks*4) - (avg_space*blocks/1024))/1024 "Used space Mb", 
(empty_blocks*4/1024)-(((blocks*4)-(avg_space*blocks/1024))/1024) 
"Space left Mb",
num_rows "Rows" , avg_row_len "Row length",CHAIN_CNT from 
user_tables where table_name='TABLE1';


Table Name  Tablespace    Avgfreespace Blocksused Emptyblocks 
----------------------------------------------------------------
TABLE1      TABLESPACE1   1613         5974       58537       


Used space Mb   Space left Mb  Rows  Rowlength
 -------------- -----------    ------------  ----------
14.1462727      214.513884     6306  2253

CHAIN_CNT  FREELISTBLOCKS
---------  --------------
     5997  3


SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)
<2000;

  COUNT(*)
----------
      2479

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>2000 
and dbms_lob.getlength(TABLE1blob)<4000;

  COUNT(*)
----------
      3577

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>4000;

  COUNT(*)
----------
       242

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)<4000;

  COUNT(*)
----------
      6064

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)<=4000;

  COUNT(*)
----------
      6064
      
SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>8000;

  COUNT(*)
----------
        47


SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>16000;

  COUNT(*)
----------
         5

SQL>select count(*)  from TABLE1 where dbms_lob.getlength(TABLE1blob)>32000;

  COUNT(*)
----------
         0      
      

SQL>select avg(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

AVG(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                            2367.24215
      

SQL>select max(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

MAX(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                                 22780
                                 
SQL>select min(dbms_lob.getlength(TABLE1BLOB))  from TABLE1 ;

MIN(DBMS_LOB.GETLENGTH(TABLE1BLOB))
--------------------------------------
                                   743                                 


SQL>exec show_space('TABLE1');
Free Blocks.............................3
Total Blocks............................64512
Total Bytes.............................264241152
Unused Blocks...........................58537
Unused Bytes............................239767552
Last Used Ext FileId....................8
Last Used Ext BlockId...................13329
Last Used Block.........................855      


SQL>column segment_name new_val lob
SQL>select segment_name from user_lobs
  2  where table_name = 'TABLE1';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

SQL>exec show_space('&lob',user,'LOB');
Free Blocks.............................2
Total Blocks............................393216
Total Bytes.............................1610612736
Unused Blocks...........................392663
Unused Bytes............................1608347648
Last Used Ext FileId....................18
Last Used Ext BlockId...................229393
Last Used Block.........................553

I have the following questions:

1. The table above has sample loading(of actual data). Actual number of 
rows will be in millions(max 20,000,000). Increases by 1,000,000 
monthly to the max specified. Most of the blob is less than 3k.
Why is there a chaint_cnt almost equal to the number of rows?
Is it getting displayed because of the ENABLE_IN_ROW ? Is it ok for it 
to be this way then? Enable in row was chosen because most of the 
data is less than 4k. I do not know how to get the space the
lob locater will hold. Out of the 1,000,000 rows per month abt 10% blobs 
will have size above 4k...to any max (inside limit).hence enable in row was
chosen.



2. I need to do a 'select distinct datecolumn' on the above table1 to get last5 days 
dates (exclude sat/sun) if soem conditions are satisfied .
      
  SQL>desc table2 (small table , abt 100 rows , wont increase much..max 300 )
Name                                      Null?    Type----------------------------------------- -------- ------------------
table2ref                                   NOT NULL RAW(16)
namecol                                      VARCHAR2(50)
    
SQL>desc table1 (huge table abt 20,000,000)
Name                                      Null?    Type
---------------------------------------- -------- ----------table1ref                                  NOT NULL RAW(16)
col1                                         VARCHAR2(58)
col2                                         VARCHAR2(60)
col3                                         VARCHAR2(80)
col5                                          VARCHAR2(80)TABLE1BLOB                                      BLOB
table2ref                              NOT NULL RAW(16)
DATEcol                                NOT NULL NUMBER(10)
     ....and so on 


       
1 Indexe on table2 - > on table2ref column
6 Indexes on table1-> on table2ref column , on col1 column, col2 column, col3 column, 
on datecol column,col5 columncol1,col2,col3,col5 are nullable.....(will index not be 
used though present because of these nullable columns?). I cannot make it not null
(a 3rd party db).

select
      DISTINCT to_char(to_date(t1.DATEcol,'j'),DD/MON/YYYY') 
   FROM
       table1 t1,
       table2 t2
   WHERE 
       t1.table2ref = t2.table2ref AND
       TO_DATE(t1.DATEcol,'j') <= (sysdate-1) AND
       TO_DATE(t1.DATEcol,'j') >= (sysdate-7) AND 
       UPPER(t2.namecol) LIKE  '%XXXX%' AND
         UPPER(t1.col1) = 'P' AND
         UPPER(t1.col5) IN ('T','O') AND
     (t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'));


Elapsed: 00:00:26.76

Execution Plan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=6 Card=1 Bytes=38)
   1    0   SORT (UNIQUE) (Cost=6 Card=1 Bytes=38)
   2    1     NESTED LOOPS (Cost=2 Card=1 Bytes=38)
   3    2       TABLE ACCESS (FULL) OF 'TABLE2' (Cost=1 Card=1 Byt
          es=22)

   4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE1' (Cost=1 C
          ard=1 Bytes=16)

   5    4         INDEX (RANGE SCAN) OF 'TABLE1_TABLE2REF_IDX'
          (NON-UNIQUE)





Statistics
----------------------------------------------------------
          0  recursive calls
          4  db block gets
      12091  consistent gets
       3837  physical reads
          0  redo size
        132  bytes sent via SQL*Net to client
        246  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          1  sorts (memory)
          0  sorts (disk)
          0  rows processed


Later i pumped in more data ..upto 100,000 rows into table1 (with last 7 days dates  
and others too). 
Execution time is more than this and the plan is the same. I can have about 
1,000,000/22= abt 500,000
records per date.
How do i reduce response time? any other way to get the dates if this is not a right approach?



3. When made another table table3 , same as table1 , copied all the columns but the 
blob into it, and executed the same
   query above on the new table, it gets executed fast...less than a second. why? 
   Similar execution plan:.
   Avg rwo length of the new table is 435. thats why?
   
   Elapsed: 00:00:00.31
   
   Execution Plan
   ----------------------------------------------------------
      0      SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=1 Bytes=35)
      1    0   SORT (UNIQUE) (Cost=38 Card=1 Bytes=35)
      2    1     NESTED LOOPS (Cost=36 Card=1 Bytes=35)
      3    2       TABLE ACCESS (FULL) OF 'TABLE2' (Cost=1 Card=1 Byt
             es=21)
   
      4    2       TABLE ACCESS (BY INDEX ROWID) OF 'TABLE3' (Cost=35
             Card=1 Bytes=14)
   
      5    4         INDEX (RANGE SCAN) OF 'TABLE3_TABLE2REF_IDX'
             (NON-UNIQUE) (Cost=6 Card=1)
Statistics
---------------------------------------------------------
          0  recursive calls
          4  db block gets
        116  consistent gets
          0  physical reads
          0  redo size
        220  bytes sent via SQL*Net to client
        314  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          7  sorts (memory)
          0  sorts (disk)
          0  rows processed

 

Tom Kyte
September 20, 2002 - 6:33 pm UTC

1) it is because of the in row storage. 4k block = less then 4k per row. You don't show the other columns but if your row exceeds about 3900/4000 bytes -- it'll be chained. This could be bad if you full scan this lots of times. If everything is a single row index read, probably OK (we'd have to do 2 IO's to put the row together, using an OUT OF LINE lob -- 3/4/5 at leas -- the row, the lob index, the lob segment)

You would be better in this case with an 8k block probably if this is the "major" table. 8k is a nice round number for a block size (4k is a bit "strange", don't see that too much)

2) indexes are precluded because you perform functions on all of the columns:

WHERE
t1.table2ref = t2.table2ref AND
TO_DATE(t1.DATEcol,'j') <= (sysdate-1) AND <<== no index path
TO_DATE(t1.DATEcol,'j') >= (sysdate-7) AND <<== no index path
UPPER(t2.namecol) LIKE '%XXXX%' AND <<== no index path
UPPER(t1.col1) = 'P' AND <<== no index path
UPPER(t1.col5) IN ('T','O') AND <<== no index path
(t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'));


do this instead (assuming your DATEcol is badly stored as a NUMBER type, lose the to_number if it is in fact a string -- why why why they didn't use a DATE type!!!!)

and t1.datecol between to_number( to_char(sysdate-1,'j') )
and to_number( to_char(sysdate-7,'j') )
and upper(t2.namecol) like '%XXX%'
and t1.col1 in ( 'P', 'p' )
and t1.col5 in ( 'T', 't', 'O', 'o' )
and (t1.col3= 'L' OR UPPER(t1.col2) IN ('R,I,U'))

that'll maximize the opportunities for index paths when appropriate.

3) yes, you did 1% of the logical ios and no physical io. it'll take alot less..

try to make it however so the index on date can actually be used!

row length less after making table as 'insert into' having LOBs

A reader, September 25, 2002 - 10:18 am UTC


Hi Tom,
Thanks for the above info.
I created another table - table_new specifying disble in row for the LOB column.
The new table was created using the same storage and tablespace clauses as table1
except for disable in row for lob in the new table (as opposed to enable in row
for lob in table1).
table1 data is in tablespace1
table1 lob is in TABLESPACE_LOB
table_new data is in tablespace1
table1_new lob is in TABLESPACE_LOB

Then I did a
insert into table_new select * from table1;

Then i analyzed the new table and found the avg row length lesser . how? No chained rows because of that.
How can the avg row length decrease in the new table?
I checked the length of teh lobs in both the tables, they are same.

here are the details:

TABLE1 has 30 columns - one blob column.
The remaining 29 columns as follows : 10 varchar2(30) , 12 number (38) , 6 raw(8), 1 char(1)

select segment_name from user_lobs where table_name='TABLE_NEW';

SEGMENT_NAME
------------------------------
SYS_LOB0000008509C00015$$


select segment_name from user_lobs where table_name='TABLE';

SEGMENT_NAME
------------------------------
SYS_LOB0000007813C00015$$

select Table_name "Table Name" ,tablespace_name "Tablespace name" ,avg_space "Avg free space", blocks "Blocks used" , empty_blocks "Empty blocks" , ((blocks*4) - (avg_space*blocks/1024)) "Used space Kb", num_rows "Rows" , avg_row_len "Row length" ,chain_cnt from user_tables;

Table Name Tablespace name Avg free space Blocks used Empty blocks Used space Kb Rows Row length CHAIN_CNT
------------------------------ ------------------------------ -------------- ----------- ------------ ------------- ---------- ---------- ----------
TABLE1 TABLESPACE1 1613 5974 58537 14485.7832 6306 2253 5997




Table Name Tablespace name Avg free space Blocks used Empty blocks Used space Kb Rows Row length CHAIN_CNT
------------------------------ ------------------------------ -------------- ----------- ------------ ------------- ---------- ---------- ----------
TABLE_NEW TABLESPACE1 472 205 818 725.507813 6306 113 0


The above queries with distinct etc work much faster because of the lesser row length.

Tom Kyte
September 25, 2002 - 11:04 am UTC

umm, the average row length of course went down cause you are no longer storing upto 4000 bytes of text INLINE, you are storing it out of line and the "pointer" (lob locator) is small -- not upto 4000 bytes.

Hence, the over all row width -- which NEVER includes out of line data like a lob -- is smaller.



A reader, September 25, 2002 - 10:25 am UTC

continuing from above,
if the row length decreased because of only the lob locators getting inserted in the new table (table_new)..then how do i make a ne wtable with disable in row instead of enable in row
using insert into table_new select * from table1?
thanks.

Tom Kyte
September 25, 2002 - 11:08 am UTC

the row length decreased for that reason, yes.


I do not understand the second part of the question?? You would create the table with DISABLE on the lob and then just insert?

can this be a way to inmprove performance?

A reader, September 25, 2002 - 10:31 am UTC

can this be a way to increase performance?
I mean, in my queries (as above using distinct etc etc)
i dont use the LOB column at all. I just need to update /retreive certain other fields .
So if i create a view on the base table (with lob) as
'select everything except blob column' ' and use that view in my queries, it will be faster?
Is this a good approach for retreiving /updating/inserting
non blob columns?


Tom Kyte
September 25, 2002 - 11:11 am UTC

You moved upto 4k of data OUT OF THE table into another segment.

Suppose you have a table t ( x int, y date, z varchar2(20), a clob )

and a is stored inline. You could find your average rowsize to be 4k now. So, if you have 100,000 rows, you have 390meg of data. If you full scan T, you'll read 390meg of data

Now, you disable storage in row. You might find your average rowsize to be 40bytes. Now, if you full scan, you'll hit 4meg of data.

400meg, 4meg -- one will scan about 100times faster.

You need not use the view to "hide" the blob -- the blob when stored out of line is very small.

A reader, September 25, 2002 - 12:07 pm UTC

Thanks Tom,
One more point that I didnt understand:

1)If i have a table t ( x int, y date, z varchar2(20), a clob ).
Two cases:
A)I store lob out of line.(disbale in row)
B)I store lob inline.(enable in row)

Now i have indexes on x and y...so when i write a query to say ...

select distinct x,y,z from t where x= somethign and y=something.

I write it such that i dont do a full table scan of t but make it use indexes on x and y.

So a) Vs b) should not make any difference right? I am not selecting LOB column at all and am not doing a full table scan either.

But this is not the case with me. If i store lob out of line
performance is much much faster(for the reasons you have already explained ). But i didnt understand how it is making a difference if i am not doing a full t scan in both cases.
The queries run very very fast now!! with disable in row.

2)When i do a count (*) on a table, does all the rows (withe all columns) get read before counting?

3)when i do a 'select distinct x,y,z from t where..' or 'select x,y,z from t where...' does all columns get read(x,y,z and a) or only x,y,z ? (assuming no full table scan).

thanks.




Tom Kyte
September 25, 2002 - 12:32 pm UTC

Wrong -- a) vs b) could make a huge difference.

Suppose for x = something and y = something there are 100 rows.

Suppose you have a 4k blocksize.

Suppose you store the data inline and it is about 3k of data/row.

a) will do about 100 logical IO's on the table and 1 (one) physical IO
b) will do about 100 logical IO's on the table and upto 100 physical IOs

that is, a could store the answer on a block, b cannot.


so, that could be your case as well. you are processing a WHOLE LOT less data on far fewer blocks when it is stored out of line.

2) well, we are counting as we go along -- they happen "at the same time"? reading ahead to q3 -- do you mean "do all of the columns get read"? No, just the head rowpiece gets read.

3) just xyz, unless A is in front of xyz that is (then we have to read over A of course to find xyz


Inserting clob performance issue: enable vs disable strorage in row

A reader, June 05, 2003 - 5:48 pm UTC

I did a test with real sized data and found, as you said that, Storing clob data out of line is as efficient as in line, however when changing some of the storage parameters like (NOCAHCE etc) I see this: (run on 8.1.7.3)

set timing on
drop table t;
create table t (
A VARCHAR2 (30) NOT NULL,
B VARCHAR2 (30) NOT NULL,
C clob default empty_clob()
)
NOCACHE
LOGGING
lob(c) store as t_name_lo(enable storage in row);

begin
for i in 1..1000 loop
insert into t (a, b, c)
select 'a','b',rpad('*',32000,'*') from dual;
end loop;
end;
/
rollback
/
Elapsed: 00:00:42.05

--FIine an these are the other results:
noCACHE,noLOGGING:
Elapsed: 00:00:42.07
--
CACHE,LOGGING
Elapsed: 00:00:42.01
--
CACHE,NOLOGGING
Elapsed: 00:00:43.01

All results are about the same, that's OK.

But when I changed the CLOB for I LONG and rerun the insert (from sqlplus):

--col c is a LONG
Elapsed: 00:00:09.08

Over 4 times Faster????

Please explain? I realise LONGs are deprecated so I want to use CLOBS, but if I have , as I do , a scenario where I just want to write once no read of the long/LOB col. Then is LONG better?

Thanks in advance







Tom Kyte
June 05, 2003 - 6:41 pm UTC

the longs be in the buffer cache.

nocache = you waited for a direct write to disk.

you asked to bypass the buffer cache.

LONGs faster for writes than LOBS

A reader, June 05, 2003 - 7:18 pm UTC

Hi Tom,

You said that the LONGs are in the buffer cache
and using nocache means you have to wait for a direct write to disk. which is fine

However I've tried the insert with all combinations:

NOCACHE,NOLOGGING: Elapsed: 00:00:42.07
CACHE,LOGGING: Elapsed: 00:00:42.01
CACHE,NOLOGGING: Elapsed: 00:00:43.01

But when I changed the CLOB for a LONG and rerun the insert
LONG: Elapsed: 00:00:09.08

LONG is still faster even when the CLOB is ion the buffer cache.

Does this mean that despite the fact that LONGS are deprecated we should still use them for write once (no update) type operations?

Thanks again




Tom Kyte
June 06, 2003 - 11:06 am UTC

well, your test is a tad "flawed".  if you check it out -- you'll discover you inserted 4000 bytes -- rpad returned 4000 bytes when used in SQL.  But in any event, I cannot reproduce.  This was run in noarchivelog mode (so nocache is nologging and cache is logging).  I cannot reproduce findings similar to yours at all.

First just with "wall clock" timings:

ops$tkyte@ORA920> drop table t_nocache;

Table dropped.

Elapsed: 00:00:00.34
ops$tkyte@ORA920> create table t_nocache (a number, b clob)
  2  lob (b) store as (enable storage in row nocache )
  3  /

Table created.

Elapsed: 00:00:00.15
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t_cache;

Table dropped.

Elapsed: 00:00:00.26
ops$tkyte@ORA920> create table t_cache (a number, b clob)
  2  lob (b) store as (enable storage in row cache )
  3  /

Table created.

Elapsed: 00:00:00.10
ops$tkyte@ORA920>
ops$tkyte@ORA920> drop table t_long;

Table dropped.

Elapsed: 00:00:01.99
ops$tkyte@ORA920> create table t_long(a number, b long);

Table created.

Elapsed: 00:00:00.09
ops$tkyte@ORA920>
ops$tkyte@ORA920>
ops$tkyte@ORA920> set timing on
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_nocache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.27
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_cache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.57
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_long ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:14.70
ops$tkyte@ORA920>

<b>
14, 3, 14 seconds....

Now using runstats and comparing the cached lob with the long</b>


ps$tkyte@ORA920> exec runstats_pkg.rs_start;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.11
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_cache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:02.53
ops$tkyte@ORA920> exec runstats_pkg.rs_middle;

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.13
ops$tkyte@ORA920> declare
  2          l_data long := rpad('*',32000,'*');
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_long ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:15.40
ops$tkyte@ORA920> exec runstats_pkg.rs_stop(1000);
Run1 ran in 259 hsecs
Run2 ran in 1541 hsecs
run 1 ran in 16.81% of the time

Name                                  Run1        Run2        Diff
STAT...free buffer requested         4,158       5,224       1,066
STAT...Elapsed Time                    271       1,545       1,274
STAT...consistent gets               2,460       6,617       4,157
LATCH.cache buffer handles               4       6,930       6,926
STAT...dirty buffers inspected           3      10,297      10,294
STAT...free buffer inspected             3      10,506      10,503
LATCH.redo allocation               10,387      21,551      11,164
STAT...redo entries                 10,324      21,506      11,182
STAT...db block changes             16,245      28,364      12,119
STAT...db block gets                22,816      37,066      14,250
LATCH.checkpoint queue latch            16      17,988      17,972
STAT...session logical reads        25,276      43,683      18,407
LATCH.cache buffers lru chain        4,159      22,959      18,800
LATCH.cache buffers chains          77,235     162,816      85,581
STAT...redo size                33,552,760  34,459,324     906,564

Run1 latches total versus runs -- difference and pct
Run1        Run2        Diff       Pct
112,677     253,011     140,334     44.53%

PL/SQL procedure successfully completed.


<b>it was faster, it was infinitely more efficient latch wise</b>

 

A reader, July 21, 2004 - 1:46 pm UTC


Cache/Logging vs NoCache/Logging?

Rafique Awan, July 26, 2004 - 8:49 pm UTC

Tom,

As you mentioned that Cache Lobs are much faster as compared to NoCache Lobs, So in this regard, I have two questions.

1) Why Default setting for Lob is NoCache/Logging?
2) Is there any overhead of changing default to Cache/Logging.

Thanks in advance.

Tom Kyte
July 27, 2004 - 7:10 am UTC

1) because logging is the default for everything (better safe than sorry) and lobs are expected to be "Large OBjects" -- something you don't want to pull into the cache to bump everything else out (a single lob instance could easily be larger than your buffer cache!)

2) depends -- many times, this is beneficial if the lobs are frequently used and on the smaller side.

Cache Clob is awesome!!!

Rafique Awan, July 30, 2004 - 11:03 pm UTC

Thanks Tom for your helps and guidance. You are really great. I changed the No_cache clob to Cache Clob and performance has been improved drastically. As you said, we should also consider to use Cache Clob if the size of the clob is on smaller side, in my case, It was 20K.

great

A reader, September 17, 2004 - 8:17 pm UTC

Hi Tom,
As you said,if the blob size is more than 4000 bytes it will use the out line store?
But suppose I am using the block size of say 16KB, the BLOB size is say 13KB and I am defining to use inline store.
What will Oracle use Inline store or out line store?
Request your suggestions and recommendations.
Thanks

Tom Kyte
September 17, 2004 - 8:31 pm UTC

4000, like a varchar2

IN-line or out-line

atul, October 06, 2005 - 1:37 am UTC

Hi,

Our block size is 8k.
We have many tables which are uding CLOB

+++++++++++
select Table_name "Table Name",
avg_row_len "Row length" ,chain_cnt from user_tables where table_name=TXN_ERPXX002'; 2

Table Name Row length CHAIN_CNT
------------------------------ ---------- ----------
TXN_ERPXX002 1126 44

++++++++++++


Show Row lenght as 1126 and chain cnt as 44.
In this situation will it be better to go for out of line clob?

Or

Why chain cnt is showing value if avg row length

Tom Kyte
October 06, 2005 - 7:45 am UTC

clobs are out of lined at 4000 bytes regardless of your setting, so this is not due to clobs causing a row to SPAN multiple blocks - rather this looks like a MIGRATED row.


search this site for migrated row if you don't know what that means...

Caching rocks! / Autotrace Issue

Uwe M. Kuechler, February 22, 2006 - 8:33 am UTC

Hi Tom,

thanks to your two hints about automatic inline storage of text <4000 Bytes and caching, I was able to take new, successful measures in tuning a query.

Now for the bad news: I used autotrace in SQL*Plus to compare run times and statistics of different approaches, but there seems to be an issue with CLOBs. When I run the query without tracing, the output of ~15,000 rows (out of 185,000) finished in about 13s. With autotrace traceonly, I get the statistics after 3 minutes! This doesn't happen when I omit the LOB column in my statement. I this a known issue? Should I build a test case for you?

Best regards,
--==/ Uwe \==--

Tom Kyte
February 22, 2006 - 9:09 am UTC

Should I build a test case for you?

yes

Su Baba, April 14, 2006 - 6:46 pm UTC

I'm trying to measure the effect of a full table scan on a table that has a LOB column storing inline vs out-of-line. Why isn't there much difference in terms of logical IOs and cost?  I was expecting both these numbers to be much higher for t_in_row which has LOBs stored inline. Is my test case flawed?


SQL> SELECT * FROM v$version;

BANNER
----------------------------------------------------------------
Oracle9i Release 9.2.0.7.0 - Production
PL/SQL Release 9.2.0.7.0 - Production
CORE    9.2.0.7.0    Production
TNS for 32-bit Windows: Version 9.2.0.7.0 - Production
NLSRTL Version 9.2.0.7.0 - Production

SQL> 
SQL> DROP TABLE t_in_row;

Table dropped.

SQL> DROP TABLE t_out_row;

Table dropped.

SQL> 
SQL> 
SQL> CREATE TABLE t_in_row (
  2  col1    VARCHAR2(30),
  3  theCLOB CLOB
  4  )
  5  LOB (theCLOB) STORE AS (ENABLE STORAGE IN ROW)
  6  /

Table created.

SQL> 
SQL> 
SQL> CREATE TABLE t_out_row (
  2  col1    VARCHAR2(30),
  3  theCLOB CLOB
  4  )
  5  LOB (theCLOB) STORE AS (DISABLE STORAGE IN ROW)
  6  /

Table created.

SQL> 
SQL> 
SQL> INSERT INTO t_in_row
  2  SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
  3  FROM   all_objects;

24333 rows created.

SQL> 
SQL> INSERT INTO t_out_row
  2  SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
  3  FROM   all_objects;

24333 rows created.

SQL> 
SQL> commit;

Commit complete.

SQL> 
SQL> 
SQL> ANALYZE TABLE t_in_row COMPUTE STATISTICS;

Table analyzed.

SQL> ANALYZE TABLE t_out_row COMPUTE STATISTICS;

Table analyzed.

SQL> 
SQL> 
SQL> col segment_name for a15
SQL> 
SQL> SELECT segment_name, bytes, blocks, extents
  2  FROM   user_segments
  3  WHERE  segment_name IN ('T_IN_ROW', 'T_OUT_ROW');

SEGMENT_NAME         BYTES     BLOCKS    EXTENTS
--------------- ---------- ---------- ----------
T_IN_ROW           3145728        384         18
T_OUT_ROW          2097152        256         17

SQL> set autotrace traceonly
SQL> 
SQL> COLUMN plan_plus_exp FORMAT a100
SQL> 
SQL> SELECT col1
  2  FROM   t_in_row;

24333 rows selected.


Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=CHOOSE (Cost=31 Card=24333 Bytes=729990)
   1    0
  TABLE ACCESS (FULL) OF 'T_IN_ROW' (Cost=31 Card=24333 Bytes=729990)


Statistics
----------------------------------------------------------
          5  recursive calls
          0  db block gets
       1919  consistent gets
          0  physical reads
          0  redo size
     318379  bytes sent via SQL*Net to client
      18345  bytes received via SQL*Net from client
       1624  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      24333  rows processed

SQL> SELECT col1
  2  FROM   t_out_row;

24333 rows selected.


Execution Plan
----------------------------------------------------------
   0
SELECT STATEMENT Optimizer=CHOOSE (Cost=25 Card=24333 Bytes=729990)
   1    0
  TABLE ACCESS (FULL) OF 'T_OUT_ROW' (Cost=25 Card=24333 Bytes=729990)


Statistics
----------------------------------------------------------
          5  recursive calls
          0  db block gets
       1863  consistent gets
          0  physical reads
          0  redo size
     318379  bytes sent via SQL*Net to client
      18345  bytes received via SQL*Net from client
       1624  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      24333  rows processed

SQL> spool off

 

Tom Kyte
April 15, 2006 - 1:02 pm UTC

SQL> SELECT segment_name, bytes, blocks, extents
  2  FROM   user_segments
  3  WHERE  segment_name IN ('T_IN_ROW', 'T_OUT_ROW');

SEGMENT_NAME         BYTES     BLOCKS    EXTENTS
--------------- ---------- ---------- ----------
T_IN_ROW           3145728        384         18
T_OUT_ROW          2097152        256         17

that would indicate that the tables are "basically the same size".


The LIO's are an artifact of your arraysize - so this, run your autotrace selects with arraysizes of

2
10
50
100
500
1000

and see what you see.  But basically, these tables are about the same size, not sure why would expect super large differences.

A lob locator takes room - not 2000 bytes, but room.  So, the tables are pretty much the same. 

Su Baba, April 15, 2006 - 5:09 pm UTC

I was expecting a large difference because I was expecting a LOB locator to take much less room comparing to 2K of data stored inline for t_in_row. I thought a full table scan on t_out_row would definitely be much faster than a full table scan on t_in_row for the above reason.

Tom Kyte
April 16, 2006 - 7:18 am UTC

I just noticed something

ops$tkyte@ORA10GR2> select 2000*24333 - 3145728 from dual;

2000*24333-3145728
------------------
          45520272


If your row is 2000 bytes
AND you have 24,333 of them
THEN you have some 45,520,272 of data (at least, you have more)
and that is less than 3,145,728 (of allocated space)

So, whats up with that?  how did that happen?

I noticed that because on my 8k blocksize database - well, it was different. 

Distinct on a row

Vin, April 16, 2006 - 10:25 am UTC

In the thread above you said for the Question
3)when i do a 'select distinct x,y,z from t where..' or 'select x,y,z from t where...' does all columns get read(x,y,z and a) or only x,y,z ? (assuming no full table scan)

and your answer was
3) just xyz, unless A is in front of xyz that is (then we have to read over A of course to find xyz.

Please correct me if I'm wrong here.
I thought when you have distinct in your select or any sql, it is applied on the entire row.
so if this is true then no matter what position of column A is, it would be read even if your query is 'select distinct x,y,z from t where..'

Appreciate your help.

Tom Kyte
April 16, 2006 - 5:50 pm UTC

no I did not - see it again, I said "no, unless..."



that is what "just xyz, UNLESS A ..."

the question was "do we read" and the answer is "if xyz appear before A, we won't read A, but if A appears before xyz, then in order to even GET to xyz, we have to read over A"



Su Baba, April 17, 2006 - 12:15 pm UTC

I reran the test again. It's strange that the avg_row_len (user_tables) for t_in_row is 75!

Is there a query you want me to run to get more info about this situation?


SQL> 
SQL> DROP TABLE t_in_row;

Table dropped.

SQL> DROP TABLE t_out_row;

Table dropped.

SQL> 
SQL> CREATE TABLE t_in_row (
  2     col1    VARCHAR2(30),
  3     theCLOB CLOB
  4  )
  5  LOB (theCLOB) STORE AS (ENABLE STORAGE IN ROW)
  6  /

Table created.

SQL> 
SQL> 
SQL> CREATE TABLE t_out_row (
  2     col1    VARCHAR2(30),
  3     theCLOB CLOB
  4  )
  5  LOB (theCLOB) STORE AS (DISABLE STORAGE IN ROW)
  6  /

Table created.

SQL> 
SQL> 
SQL> INSERT INTO t_in_row
  2  SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
  3  FROM   all_objects;

27675 rows created.

SQL> 
SQL> INSERT INTO t_out_row
  2  SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
  3  FROM   all_objects;

27675 rows created.

SQL> 
SQL> commit;

Commit complete.

SQL> 
SQL> 
SQL> ANALYZE TABLE t_in_row COMPUTE STATISTICS;

Table analyzed.

SQL> ANALYZE TABLE t_out_row COMPUTE STATISTICS;

Table analyzed.

SQL> 
SQL> 
SQL> SELECT COUNT(*)
  2  FROM   t_in_row;

  COUNT(*)
----------
     27675

SQL> 
SQL> 
SQL> SELECT COUNT(*)
  2  FROM   t_out_row;

  COUNT(*)
----------
     27675

SQL> 
SQL> 
SQL> col segment_name for a15
SQL> 
SQL> SELECT segment_name, bytes, blocks, extents
  2  FROM   user_segments
  3  WHERE  segment_name IN ('T_IN_ROW', 'T_OUT_ROW');

SEGMENT_NAME         BYTES     BLOCKS    EXTENTS
--------------- ---------- ---------- ----------
T_IN_ROW           3145728        384         18
T_OUT_ROW          2097152        256         17

SQL> 
SQL> 
SQL> SELECT table_name, num_rows, blocks, avg_space, avg_row_len
  2  FROM   user_tables
  3  WHERE  table_name IN ('T_IN_ROW', 'T_OUT_ROW');

TABLE_NAME                       NUM_ROWS     BLOCKS  AVG_SPACE AVG_ROW_LEN
------------------------------ ---------- ---------- ---------- -----------
T_IN_ROW                            27675        306       1106          75
T_OUT_ROW                           27675        244       1605          55

SQL> 
SQL> spool off

 

Tom Kyte
April 17, 2006 - 8:06 pm UTC

what is your block size.

and please stop using analyze table compute/estimate - use dbms_stats.

Su Baba, April 17, 2006 - 8:47 pm UTC

We only use one block size: 8K.

SQL> show parameter db_block

NAME                                 TYPE        VALUE
------------------------------------ ----------- -----
db_block_buffers                     integer     0
db_block_checking                    boolean     FALSE
db_block_checksum                    boolean     TRUE
db_block_size                        integer     8192

 

Tom Kyte
April 18, 2006 - 9:27 am UTC

version - I cannot reproduce. use dbms_metadata.get_ddl to verify the ddl says out of line for real.

Su Baba, April 18, 2006 - 12:03 pm UTC

SQL> select * from v$version;

BANNER
------------------------------------------------------
Oracle9i Release 9.2.0.7.0 - Production
PL/SQL Release 9.2.0.7.0 - Production
CORE    9.2.0.7.0       Production
TNS for 32-bit Windows: Version 9.2.0.7.0 - Production
NLSRTL Version 9.2.0.7.0 - Production

SQL> select dbms_metadata.get_ddl('TABLE','T_OUT_ROW') txt from dual;

TXT
-----------------------------------------------------------------------

  CREATE TABLE "PKLIN"."T_OUT_ROW"
   (    "COL1" VARCHAR2(30 CHAR),
        "THECLOB" CLOB
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "USERS"
 LOB ("THECLOB") STORE AS (
  TABLESPACE "USERS" DISABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
  NOCACHE
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT))

SQL> select dbms_metadata.get_ddl('TABLE','T_IN_ROW') txt from dual;

TXT
-----------------------------------------------------------------------

  CREATE TABLE "PKLIN"."T_IN_ROW"
   (    "COL1" VARCHAR2(30 CHAR),
        "THECLOB" CLOB
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "USERS"
 LOB ("THECLOB") STORE AS (
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
  NOCACHE
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT))
 

Tom Kyte
April 18, 2006 - 2:46 pm UTC

it obviously stored it out of line (you can verify yourself by looking at the size of the lob segment and lob index).

I cannot reproduce - in 9206 on redhat.

I do not have 9207 on windows - you'll need to follow that up with support to determine that cause - on my 10g install on windows - it is 83mb for 27k rows - and the data is "inlined". On my 9ir2 installs on linux - it is 83mb when stored inline on 8k blocks.

Just had a thought: what is your characterset?

Su Baba, April 24, 2006 - 8:09 pm UTC

Both tables have the same lobsegment size, but very different lobindex size. Also inserting into t_in_row was much faster than inserting into t_out_row--almost twice the speed.

col value for a20

SELECT *
FROM nls_database_parameters
WHERE parameter = 'NLS_CHARACTERSET';

PARAMETER VALUE
------------------------------ --------
NLS_CHARACTERSET AL32UTF8


col table_name for a12
col segment_name for a25
col index_name for a24
col segment_type for a10

SELECT b.table_name, a.segment_name, a.segment_type, a.bytes, a.blocks, a.extents
FROM user_segments a, user_lobs b
WHERE b.table_name IN ('T_IN_ROW', 'T_OUT_ROW') AND
a.segment_name = b.segment_name
UNION ALL
SELECT b.table_name, a.segment_name, a.segment_type, a.bytes, a.blocks, a.extents
FROM user_segments a, user_lobs b
WHERE b.table_name IN ('T_IN_ROW', 'T_OUT_ROW') AND
a.segment_name = b.index_name;


TABLE_NAME SEGMENT_NAME SEGMENT_TY BYTES BLOCKS EXTENTS
------------ ------------------------- ---------- ---------- ---------- ----------
T_IN_ROW SYS_LOB0000028215C00002$$ LOBSEGMENT 234881024 28672 99
T_OUT_ROW SYS_LOB0000028218C00002$$ LOBSEGMENT 234881024 28672 99
T_IN_ROW SYS_IL0000028215C00002$$ LOBINDEX 65536 8 1
T_OUT_ROW SYS_IL0000028218C00002$$ LOBINDEX 2097152 256 17


TRUNCATE TABLE t_in_row;
TRUNCATE TABLE t_out_row;


set timing on

INSERT INTO t_in_row
SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
FROM all_objects;

27699 rows created.

Elapsed: 00:00:25.02

INSERT INTO t_out_row
SELECT RPAD('x', 30, 'x'), RPAD('y', 2000, 'y')
FROM all_objects;

27699 rows created.

Elapsed: 00:00:42.00

commit;



Cached CLOB

Florin, June 13, 2006 - 12:09 pm UTC

Hi Tom,

I did a test as you mentioned in one of your examples here and indeed with the "cached lobs" it is much faster.

I want to benefit from this "feature" for imports.

Can I "alter" an existing table something like:

"alter table t1 lob (b) store as (enable storage in row cache )" ?

Many thanks in advance.


Tom Kyte
June 13, 2006 - 12:32 pm UTC

ops$tkyte@ORA10GR2> create table t ( y clob );
 
Table created.
 
ops$tkyte@ORA10GR2> select dbms_metadata.get_ddl( 'TABLE', 'T' ) from dual;
 
DBMS_METADATA.GET_DDL('TABLE','T')
-------------------------------------------------------------------------------
 
  CREATE TABLE "OPS$TKYTE"."T"
   (    "Y" CLOB
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "USERS"
 LOB ("Y") STORE AS (
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
  NOCACHE LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT))
 
 
 
ops$tkyte@ORA10GR2> alter table t modify lob(y) (cache);
 
Table altered.
 
ops$tkyte@ORA10GR2> select dbms_metadata.get_ddl( 'TABLE', 'T' ) from dual;
 
DBMS_METADATA.GET_DDL('TABLE','T')
-------------------------------------------------------------------------------
 
  CREATE TABLE "OPS$TKYTE"."T"
   (    "Y" CLOB
   ) PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "USERS"
 LOB ("Y") STORE AS (
  TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 10
  CACHE
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT))
 
 

analyze vs dbms_stats in 8i-9i for lobs

A reader, June 13, 2006 - 11:48 pm UTC

As far as I know, analyze is the only way in pre-10g (actually 10.2) db's to get the correct rowsize for LOBS. See bug 1954150 for details. dbms_stats doesn't calculate the correct row size value for in or out of line LOBS.

Cached CLOB

Florin, June 14, 2006 - 4:37 am UTC

Hi Tom,

Thanks a lot for your prompt reply!

Would you please explain me what actualy does "cache" ?

Is it as I pin the tables into buffer_keep?

Many thanks in advance!

Tom Kyte
June 14, 2006 - 8:57 am UTC

normally, a lob is nocache - every "read" of a lob by a client (when the lob is out of line) is a physical IO. every "write" of a lob is a physical IO.

the lob is not in the buffer cache.

If you make the lob cache, it can be cached in the buffer cache like anything else - it is not "pinned", it is just cacheable.

inline LOBs

A reader, June 14, 2006 - 9:43 am UTC

Hi Tom,

So, if I want to speed up my import process, will be better to have all the LOBs INLINE?

How can I do this?

Thanks!

Tom Kyte
June 14, 2006 - 12:36 pm UTC

lobs will only be inline upto 4000 bytes - doesn't make sense.

No, that would not in general speed up your import.

IMP will always be row by row with longs/lobs - just a fact, and nothing will change that.

insert in long raw is better than blob

Reene, February 05, 2008 - 7:47 am UTC

Hi Tom

in your followup above on June 6, 2003 - 11am US/Eastern:

you have shown that insert into lob (cache) performs better than the long .

i did try with the long raw - but it appears that it is faster than the blob insert.

i also read into the application developer guide:here
http://download-west.oracle.com/docs/cd/B10501_01/appdev.920/a96591/adl06faq.htm#127729
-- it says that long raw insert perfoms better than blob.

Why is Inserting into BLOBs Slower than into LONG Raws?
Question
Why is writing into BLOBs slower than inserting into LONG RAWs?

Answer
It is true that inserting data in BLOBs with JDBC Thin is slower as it still uses the DBMS_LOB package. With JDBC OCI the inserts are faster as native LOB APIs are used.

what is your thought on this.

i have a oracle 10gr2 database and there are many tables having long raw data type. the insert into these tables shows as top resource consumer (i/o) .so was trying the option of converting the long raws into blob , but during the test cases , i found long raw insert is better than blob insert.

just took your examples and tested.

long raw inserts :
-----------------

PTUSER@DEVCPQ3-308>declare
2 l_data long raw ;
3 begin
4 select rawtohex(rpad('*',32000,'*')) into l_data from dual ;
5 for i in 1 .. 1000
6 loop
7 insert into t_long ( a, b ) values ( i, l_data );
8 end loop;
9 commit;
10 end;
11 /
Elapsed: 00:00:00.08
PTUSER@DEVCPQ3-308>drop table t_long;
Elapsed: 00:00:00.04
PTUSER@DEVCPQ3-308>create table t_long(a number, b long raw) tablespace SLTC_QUOTEDAT01;
Elapsed: 00:00:01.03
PTUSER@DEVCPQ3-308>declare
2 l_data long raw := utl_raw.cast_to_raw( rpad( '0', 32123, '0' ) );
3 begin
4 --select rawtohex(rpad('*',32000,'*')) into l_data from dual ;
5 for i in 1 .. 1000
6 loop
7 insert into t_long ( a, b ) values ( i, l_data );
8 end loop;
9 commit;
10 end;
11 /
Elapsed: 00:00:02.04

--
--blob inserts.

PTUSER@DEVCPQ3-308>create table t_cache (a number, b clob) tablespace SLTC_QUOTEDAT01
2 lob (b) store as (enable storage in row cache )
3 /
Elapsed: 00:00:00.04
PTUSER@DEVCPQ3-308>declare
2 l_data long := rpad('*',32000,'*');
3 begin
4 for i in 1 .. 1000
5 loop
6 insert into t_cache ( a, b ) values ( i, l_data );
7 end loop;
8 commit;
9 end;
10 /
Elapsed: 00:00:06.01
--
Tom Kyte
February 05, 2008 - 10:16 am UTC

my thoughts on the doc quote is that it is a) out of date b) not true anymore c) only would have applied to the THIN JDBC driver (meaning, he, it would be easy to not have it be an issue in the past)


Use TKPROF.


ops$tkyte%ORA10GR2> create table t_long(a number, b long raw) ;

Table created.

ops$tkyte%ORA10GR2> create table t_cache (a number, b blob)
  2    lob (b) store as (enable storage in row cache )
  3  /

Table created.




ops$tkyte%ORA10GR2> declare
  2              l_data long raw := utl_raw.cast_to_raw( rpad( '0', 32123, '0' ) );
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_long ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> declare
  2          l_data long raw := utl_raw.cast_to_raw( rpad( '0', 32123, '0' ) );
  3  begin
  4          for i in 1 .. 1000
  5          loop
  6                  insert into t_cache ( a, b ) values ( i, l_data );
  7          end loop;
  8          commit;
  9  end;
 10  /

PL/SQL procedure successfully completed.


INSERT INTO T_LONG ( A, B ) VALUES ( :B2 , :B1 )

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      3.47       4.26          4       5130      29440        1000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1001      3.47       4.26          4       5130      29440        1000

Elapsed times include waiting on following events:
  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  db file sequential read                         4        0.02          0.02
  rdbms ipc reply                                 4        0.04          0.05
  log file switch completion                      3        0.04          0.07
********************************************************************************
INSERT INTO T_CACHE ( A, B ) VALUES ( :B2 , :B1 )


call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.00       0.00          0          0          0           0
Execute   1000      3.33       4.22          0       3462      22119        1000
Fetch        0      0.00       0.00          0          0          0           0
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total     1001      3.33       4.22          0       3462      22119        1000

i get the similar results

Reene, February 06, 2008 - 12:11 am UTC

Thank you Tom.


I tried again using tkprof - here is my results

INSERT INTO T_LONG ( A, B )
VALUES
( :B2 , :B1 )


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1000 0.62 1.95 0 338 23748 1000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1001 0.62 1.95 0 338 23748 1000


INSERT INTO T_CACHE ( A, B )
VALUES
( :B2 , :B1 )


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1000 0.47 1.63 0 7444 21914 1000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1001 0.47 1.63 0 7444 21914 1000

why query or cr so high in case of insert into t_cache.
(comarision to t_long).

is there anything i'm doing wrong .
Tom Kyte
February 06, 2008 - 7:42 am UTC

did you run what I ran, I was confused by your example where you seemed to be comparing clobs - not blobs.


show me what I show you guys - the entire thing, full disclosure, no guessing.

Cache Clobs (around 500K)

Sam, February 07, 2008 - 5:36 pm UTC

Hi Tom,

I have an update statement that is run frequently through our application. One of the columns is a clob. The average size of the clob is 500K. Our sga is 16 GB ona 12 cpu machine. DB is 10gR2.

In this case setting the clob to cache is probably not a good idea as it will not scale well and cause other parts of the application sql to fall out of the buffer cache.

The ADDM reports that HW Enque contention is the biggest wait for this update.

UPDATE ORDERUPDATE SET EXTENDEDATTRS = :B7 , CURRENTSESSIONID = :B6 , LASTUPDATEDATE = :B5 , CURRENTSTATE = :B4 , UPDATESTATUSTERMID = :B3 , UPDATEDBY_FK = :B2 WHERE OBJECTID = :B1



Enqueue Type (Request Reason): HW-Segment High Water Mark
Requests: 11,608
Succ Gets: 11616
Failed Gets: 0
Waits: 6622
Wt Time (s): 8353
Av Wt Time(ms): 1261.45


We are using LMTs. Should we pre-allocate extents even though we are using LMTs ?

Is there any way to get the HW enq waits down ?

Any help is appreciated.

Thanks
Tom Kyte
February 07, 2008 - 9:23 pm UTC

If this is happening due to segment extension, alter table allocate extent (instance N) could help.

However, it can also happen during lob space reclamation - which you are doing big time if you update a 500k clob frequently. There you would review the pctversion setting - the smaller the setting, the less this will be an issue. You need pctversion for to avoid ora-1555 - but if you don't have read consistency issues on this (eg: multiple sessions do not open the same lob locator and modify it concurrently a lot), setting lower than 10 (default) might be safe.

Allocate extents to clob segment

Sam, February 08, 2008 - 2:45 pm UTC

Hi Tom,

Thank you for your quick response.

How do we allocate extents to a LOB segment ?
alter table allocate... will allocate to the main table segment.

In this case each session works with one row in the table. The row is not read or modified by another session until the first session is done with it.

Thanks
Sam
Tom Kyte
February 08, 2008 - 3:39 pm UTC

have you determined that is is extent allocation, are these segments extending like crazy - if not - let us not bother.

CLOB segments extents

Sam, February 08, 2008 - 4:52 pm UTC

Hi Tom,

drop table orderupdate;

CREATE TABLE ORDERUPDATE
(
  OBJECTID              NUMBER(19)              NOT NULL,
  EXTENDEDATTRS         VARCHAR2(4000 BYTE),
  CURRENTSESSIONID      VARCHAR2(255 BYTE),
  STARTDATE             TIMESTAMP(3),
  LASTUPDATEDATE        TIMESTAMP(9),
  UPDATESTATUSTERMID    VARCHAR2(255 BYTE),
  CURRENTSTATE          CLOB,
  USERID                VARCHAR2(255 BYTE),
  USELOCKUNTILTIME      TIMESTAMP(9),
  MACHINEID             VARCHAR2(255 BYTE),
  MACHINELOCKUNTILTIME  TIMESTAMP(9),
  WORKORDER_FK          NUMBER(19),
  UPDATEDBY_FK          NUMBER(19)
)
TABLESPACE SPI2_VW_H
LOGGING 
NOCOMPRESS 
LOB (CURRENTSTATE) STORE AS 
      ( TABLESPACE  W_CLOB 
        DISABLE      STORAGE IN ROW
        CHUNK       8192
        PCTVERSION  0
        NOCACHE
      )
NOCACHE
PARALLEL
MONITORING;

ALTER TABLE ORDERUPDATE ADD (
  PRIMARY KEY
 (OBJECTID)
    USING INDEX 
    TABLESPACE W_INDX);




I ran the update in question through the application. The update was run 100 times with an average execution time of 3.5 secs. Most of the 100 executions ran for a single record in the table.

select segment_name, segment_type, bytes, blocks, extents
from dba_segments
where owner = 'XXX'
AND tablespace_name = 'W_CLOB';

segment_type  bytes   blocks   extents
------------ ------  ------  -------
LOBINDEX 65536  8  1
LOBINDEX 65536  8  1
LOBINDEX 65536  8  1
LOBINDEX 65536  8  1
LOBINDEX 65536  8  1
LOBINDEX 65536  8  1
LOBSEGMENT 2097152  256  17
LOBSEGMENT 65536  8  1
LOBSEGMENT 1048576  128  16
LOBSEGMENT 65536  8  1
LOBSEGMENT 524288  64  8
LOBSEGMENT 65536  8  1



From AWR for same time period:


Event: enq: HW - contention  
Waits: 9  
Time(s): 10  
Avg Wait(ms): 1,077  
% Total Call Time: 2.3


This tells me that extent allocation is an issue here. pctversion is set to 0 as each row is accessed by one session only. In reality this statement is run thousands of times an hour by the application.

Thanks


Setting Extents for clob segment

Sam, September 19, 2008 - 5:15 pm UTC

Hi Tom,

Pleas take a look at the above 2 posts and let me know how to allocate extents to a clob column as we are still seeing enq HW waits in production.

I have searched a lot of documentation, but was unable to find the syntax for it.

Thanks for you time.

Thanks
Tom Kyte
September 19, 2008 - 6:04 pm UTC

frankly, if it is extending so often that you actually can observe lots of waits on it - allocating more is only pushing the wall back a tiny bit.

How big are these segments and what are the attributes of the existing tablespaces they are in (locally managed or not, uniform size of <?> or autoallocate)

Tablespace and table settings

Sam, September 21, 2008 - 11:46 am UTC

Tom:

Thank you for your prompt reply.

Here is the ddl for the table and tablespaces.


CREATE TABLE "VWDATA"."ORDERUPDATE" 
( 
"OBJECTID" NUMBER(19), 
"EXTENDEDATTRS" VARCHAR2(4000), 
"INITIALSTATE" CLOB, "USERID" VARCHAR2(255), 
"WORKORDER_FK" NUMBER(19), 
"UPDATEDBY_FK" NUMBER(19), 
"CURRENTSTATE" CLOB, 
CONSTRAINT "ORDERUPDATEUPDATEDBY1_FKC" 
FOREIGN KEY ("UPDATEDBY_FK") REFERENCES "VWDATA"."PARTY" ("OBJECTID") VALIDATE , 
CONSTRAINT "ORDERUPDATEWORKORDER1_FKC" 
FOREIGN KEY ("WORKORDER_FK") REFERENCES "VWDATA"."WORKORDER" ("OBJECTID") VALIDATE , 
CONSTRAINT "ORDERUPDATE_WORKORD1_UNK" UNIQUE ("WORKORDER_FK") VALIDATE , 
PRIMARY KEY ("OBJECTID") VALIDATE 
) 
TABLESPACE "SPI2_VW_H" 
PCTFREE 10 INITRANS 64 MAXTRANS 255 
STORAGE ( INITIAL 64 k BUFFER_POOL DEFAULT) LOGGING 
LOB ("INITIALSTATE") STORE AS 
( TABLESPACE "SPI2_VW_CLOB" DISABLE STORAGE IN ROW CHUNK 8192 
STORAGE ( INITIAL 8388608 k BUFFER_POOL DEFAULT)
PCTVERSION 5 NOCACHE LOGGING ) 
LOB ("CURRENTSTATE") STORE AS 
( TABLESPACE "SPI2_VW_CLOB" DISABLE STORAGE IN ROW CHUNK 8192 
STORAGE ( INITIAL 10485760 k BUFFER_POOL DEFAULT)
PCTVERSION 5 NOCACHE LOGGING 
);


CREATE SMALLFILE TABLESPACE "SPI2_VW_CLOB" 
DATAFILE '/path/data1.dbf' 
SIZE 20000M REUSE 
LOGGING 
EXTENT MANAGEMENT LOCAL 
SEGMENT SPACE MANAGEMENT AUTO;

CREATE SMALLFILE TABLESPACE "SPI2_VW_H" 
DATAFILE '/path/data2.dbf' SIZE 30720M REUSE 
LOGGING 
EXTENT MANAGEMENT LOCAL 
SEGMENT SPACE MANAGEMENT AUTO;



I dropped and re-created the table allocating bigger sizes to the clobs. The segments now show:
NAME   TYPE       BYTES       BLOCKS     EXTENTS
------------------------------------------------
S$$    LOBINDEX   436207616   53248      52
S$$    LOBINDEX   536870912   65536      64
S$$    LOBSEGMENT 8589934592  1048576    128
S$$    LOBSEGMENT 10770972672 1314816    161


I still see enq: HW Contention

Please let me know what I can do to remedy this situation.

Thanks for your time.
Tom Kyte
September 21, 2008 - 2:23 pm UTC

tell us the numbers here, what are the actual figures, how much wait over what duration (cause I'll go back to - hey, you know what, if you are extending that fast - so fast to see something actually measurable here.....)


and you might not want to use ASSM (automatic segment space management) for lob tablespaces - they are designed to waste space to increase concurrency - but with lobs - a block an entirely dedicated to a single chunk, there are no concurrency issues.

Thank You

Sam, September 23, 2008 - 12:15 pm UTC

Hi Tom:

Thank you for your quick response. We had a tar open on metalink for this issue.

The analyst said that this is related to Bug 6376915. But you are right (as usual) this would also have been fixed by switching ASSM to Manual and increasing freelists.

After applying the patch, the HW enq contention was still present, but at a much lower level.

Since then we made application changes and switched the Clob to a blob by compressing the clob data in the application. We have not seen the waits since, but the waits mught popup again as load increases.

Thank You for your help.

some more help on storage

VS, April 16, 2009 - 1:48 pm UTC

Dear Tom,

good day to you as always. We are working on design of an application and if you can please give your inputs on the below.

Our application will be receiving message from a third party application on MQ, component of our application on will be calling Stored Procedure in DB to insert the messages in table. The message will be stored in CLOB data type column. There will be multiple sessions formed by the server using connection pooling and each one calling the Stored procedure and passing messages.

We will be storing some other information like message id , date and status ( will be updated after processing from another procedure). I am thinking of using disable in row storage of CLOB as a start, will you please recommend some storage settings for this particular table we should take care of from start so that there's less/no contention for the blocks of this table. We will be using either 10g/11g.

We will be doing benchmarking in next few days but if you can suggest some settings to be taken care of as base or any other pointers it will be really helpful.

Thanks for your help and time on this.

Regards,
VS.
Tom Kyte
April 16, 2009 - 2:38 pm UTC

why wouldn't you use AQ?


in general:

use ASSM (automatic segment space management) in a locally managed tablespace with system allocated extents. The only storage related settings you would consider at that point would be pctfree (if you update and grow the rows over time) and initrans (if you expect lots of transactions hitting the same block simultaneously)

Size of table

Anil, July 30, 2009 - 11:38 am UTC

Hello Tom,
We have a table with 3 CLOB columns in which 2 are stored as IN ROW and one as OUT ROW in Oracle 9i. Iam trying to understand why is the size of the table so huge.

CREATE TABLE EMAIL_DATA
(
<<other columns>>.......
SUBJECT1 CLOB,
SUBJECT2 CLOB,
SUBJECT3 CLOB
TABLESPACE TS_LOG
PCTUSED 40
PCTFREE 20
INITRANS 20
MAXTRANS 255
STORAGE (
INITIAL 40K
NEXT 1M
MINEXTENTS 1
MAXEXTENTS 2147483645
PCTINCREASE 0
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL KEEP
)
LOGGING
NOCOMPRESS
LOB (SUBJECT1) STORE AS
( TABLESPACE TS_LOG
ENABLE STORAGE IN ROW
CHUNK 8192
PCTVERSION 25
NOCACHE
STORAGE (
INITIAL 40K
NEXT 50M
MINEXTENTS 1
MAXEXTENTS 2147483645
PCTINCREASE 0
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
)
LOB (SUBJECT2) STORE AS
( TABLESPACE TS_LOG
ENABLE STORAGE IN ROW
CHUNK 8192
PCTVERSION 10
NOCACHE
STORAGE (
INITIAL 40K
NEXT 50M
MINEXTENTS 1
MAXEXTENTS 2147483645
PCTINCREASE 0
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
)
LOB (SUBJECT3) STORE AS
( TABLESPACE TS_LOG
DISABLE STORAGE IN ROW
CHUNK 8192
PCTVERSION 10
NOCACHE
STORAGE (
INITIAL 40K
NEXT 50M
MINEXTENTS 1
MAXEXTENTS 2147483645
PCTINCREASE 0
FREELISTS 1
FREELIST GROUPS 1
BUFFER_POOL DEFAULT
)
)
NOCACHE
NOPARALLEL
NOMONITORING
/

select sum(bytes)/1024/1024/1024
from user_segments
where segment_name ='EMAIL_DATA'

15.5

select sum(bytes)/1024/1024/1024
from user_segments
where segment_name in (
select segment_name from user_lobs
where table_name ='EMAIL_DATA')

17.6


select segment_name, sum(bytes)/1024/1024/1024
from user_segments
where segment_name in (select segment_name from user_lobs where table_name ='EMAIL_DATA')
group by segment_name

SYS_LOB0000431291C00026$$ 11.96
SYS_LOB0000431291C00176$$ 5.12
SYS_LOB0000431291C00208$$ 0.46


select
sum(dbms_lob.getlength(SUBJECT1))/1024/1024/1024
from EMAIL_DATA
where dbms_lob.getlength(SUBJECT1) > 4000

6.5

select
sum(dbms_lob.getlength(SUBJECT2))/1024/1024/1024
from EMAIL_DATA
where dbms_lob.getlength(SUBJECT2) > 4000

3.7

The CLOB column which has been stored as OUT ROW is occupying only 500MB as only 48 rows have data out of 1575211 rows.

The space occupied by non-CLOB columns is less than 1GB only (select sum(vsize(col1)) + sum(vsize(col2) .... from table).

Following are my question:

1. Can we say the space occupied by 2 CLOB columns (for column length less than 4000 characters are stored with the table data) is around 14.5GB (15.5GB - 1GB)?

2. And where column length is more than 4000 characters are stored in LOB segment (which are shown in USER_LOBS) occupies 17.6GB of space. Is my understanding correct?

3. Does this mean sum(dbms_lob.getlength(SUBJECT1))/1024/1024/1024 which is 6.5GB in fact is occupying 11.96GB of space and for sum(dbms_lob.getlength(SUBJECT2))/1024/1024/1024
which is 3.7GB is occupying 5.12GB?


Thanks

Tom Kyte
August 03, 2009 - 5:36 pm UTC

1) where did you get those numbers from? I didn't follow your math

it is "about 4000", that doesn't mean exactly, precisely 4000 - and remember your chunksize is 8k, each lob will always be some multiple of 8k - that chunk is used by A LOB, never across them.


and you don't say anything about your tablespace, hopefully those storage clauses are NOT RELEVANT as you are using locally managed - but you don't tell us

some more help on clob storage parameters

JV, January 04, 2010 - 11:02 am UTC

Dear Tom,
first of all a Very Happy and Prosperous New Year to you and your family.

I am not sure if this is the best thread for my question and I have multiple questions related to same problem so hope it's fine.

We have a table which has two xmltype columns both are with storage in row, out of these two one is updated as the processing of the records goes on and the other is not changed, the amount of data in both the columns for a row will be varying length. Below are the statistics of the table

Blocks 72991
Rows 80728
empty blocks 462
avg space 3259
chain cnt 28326
avg row len 412

As it can be seen there are chained/migrated rows. My questions are as follows.

1. If the due to limitation of size (4000 characters) the clob is automatically being stored out of row ( my guess as the avg row len is very less) why do we have chained/migrated rows, also am I right on my guess.

2. performing alter table move doesn't resolve the row chaining/migration, I think I will have to move the rows to another table, delete the rows and re-insert, have you faced a situation like this?

3. this is little out of the main topic, but have you faced situations where in due to chained rows the index is avoided for table. I understand that this is quite generic question but if you have faced any such situation can you please share how to investigate if due to chaining the index is being avoided it will be very helpful.

We are using 10g on Solaris platform, you help in this regard is much appreciated as always.

Regards,
JV


Tom Kyte
January 04, 2010 - 12:37 pm UTC

1) not due to that - not due to automatically being stored out of line.

2) sounds like you have rows that exceed the block size then. which would be easy with two clobs inline and some other columns.


3) chained rows is not used as a metric by the optimizer. chain_cnt is not even computed by dbms_stats.

clustering factors are perhaps the most important metric.



this shows that

a) row chaining could easily be happening with rows that just "do not fit" in your schema.

b) alter table t move would correct the ones that are correctable

ops$tkyte%ORA11GR2> create table t
  2  ( x char(200),
  3    y char(200),
  4    a clob,
  5    b clob
  6  )
  7  /

Table created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> insert into t values ( 'x', 'y', rpad( '*', 3800, '*' ), rpad('+',3800,'+') );

1 row created.

ops$tkyte%ORA11GR2> insert into t select 'x','y', null,null from dual connect by level <= 1000;

1000 rows created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> analyze table t compute statistics;

Table analyzed.

ops$tkyte%ORA11GR2> select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE, CHAIN_CNT, AVG_ROW_LEN from user_tables where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ------------ ---------- ---------- -----------
      1001         65            7       1684          1         413

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> truncate table t;

Table truncated.

ops$tkyte%ORA11GR2> insert into t select 'x','y', null,null from dual connect by level <= 1000;

1000 rows created.

ops$tkyte%ORA11GR2> analyze table t compute statistics;

Table analyzed.

ops$tkyte%ORA11GR2> select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE, CHAIN_CNT, AVG_ROW_LEN from user_tables where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ------------ ---------- ---------- -----------
      1000         65            7       1809          0         405

ops$tkyte%ORA11GR2> update t set a = rpad( '*', 3000, '*' ), b = rpad( '+', 3000, '+' );

1000 rows updated.

ops$tkyte%ORA11GR2> analyze table t compute statistics;

Table analyzed.

ops$tkyte%ORA11GR2> select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE, CHAIN_CNT, AVG_ROW_LEN from user_tables where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ------------ ---------- ---------- -----------
      1000       1000           24       1546        941        6489

ops$tkyte%ORA11GR2> alter table t move;

Table altered.

ops$tkyte%ORA11GR2> analyze table t compute statistics;

Table analyzed.

ops$tkyte%ORA11GR2> select NUM_ROWS, BLOCKS, EMPTY_BLOCKS, AVG_SPACE, CHAIN_CNT, AVG_ROW_LEN from user_tables where table_name = 'T';

  NUM_ROWS     BLOCKS EMPTY_BLOCKS  AVG_SPACE  CHAIN_CNT AVG_ROW_LEN
---------- ---------- ------------ ---------- ---------- -----------
      1000       1024            0       1561          0        6483




so, go ahead and list the chained rows - get their rowids, copy them into another table and peek at them - see how big THEY are.

move lob to out-of-line storage. any code change required ?

Dan, March 24, 2010 - 6:10 pm UTC

Tom

I need to move some lob columns to out of line storage permanently due to larger lob size.

Does it involve any code change at application level or client side ?

Currently LOB is manipulated via JDBC code.

Thanks
Tom Kyte
March 26, 2010 - 11:12 am UTC

no, it doesn't. lobs move in/out of line all by themselves naturally regardless of how they are defined (enable storage in row doesn't PROMISE to store in the row, once the lob gets over a certain very small size - it goes out of line)

BLOB Performance with Partition

Abhisek, March 21, 2011 - 3:04 am UTC

Hi Tom,

I heasrd that we can put BLOB in partitions. But I am not very sure what is this all about.

Could you please suggest if we can improve the performance of a table when the BLOB column is kept with partition?

I am not very sure of this concept so could not provide you an example. Apologies for that.
Tom Kyte
March 21, 2011 - 9:56 am UTC

Could you please suggest if we can improve the performance of a table when the
BLOB column is kept with partition?


Since you always access LOBS via a pointer ( a lob locator ) - they are not stored in the table itself (unless they are inlined and less than 4000 bytes) - partitioning them will not make them any faster - nor - any slower.

You access a blob by following a pointer to an index that gets the lob chunks, chunk by chunk. Whether it is in a single segment or a thousand partitions, the access is the same - pointer to index to chunks


You would be using partitioning here to make administration easier, faster, more manageable. For example, if you have 1tb of lob data and want to 'reorganize it', you need 1tb of free space to work with (maybe more). If you have 1tb stored in a table partitioned into 10 equi-sized pieces, you would need about 100gb of free space as you reorganize each partition by itself. Archiving and purging could likewise benefit from partitioning.


With the exception of full scans of tables (you never full scan a lob) - partitioning doesn't really make queries "faster".

Another Question

Abhisek, March 21, 2011 - 10:35 am UTC

Hi Tom,

Thanks for the explanation.

In which case, do we partition the LOB tables then?

Does it help in performance when we put the BLOB columns in a seperate tablespace?
Tom Kyte
March 21, 2011 - 12:19 pm UTC

In which case, do we partition the LOB tables then?


Hmmm, I tried to pre-emptively answer that...

I did pre-emptively answer that - re-read my answer above. I told you when/why you might partition a table with LOBS - it is all about ease of administration.


Does it help in performance when we put the BLOB columns in a seperate
tablespace?


No more so than the performance difference you get by putting indexes in a separate tablespace from the tables they are associated with - meaning:

NO DIFFERENCE REALLY.


Tablespaces are not about performance, they are about ease of administration. Your goal is "even IO over all of your devices" - ASM achieves that, raid striping achieves that, multiple non-striped datafiles in a single tablespace achieve that.

Thanks for the reply...

Abhisek, March 21, 2011 - 5:36 pm UTC


Clarification on 16k block and in-line lob storage

A reader, April 07, 2011 - 12:12 am UTC

Just the follow up of your below comment.
Hi Tom,
As you said,if the blob size is more than 4000 bytes it will use the out line store?
But suppose I am using the block size of say 16KB, the BLOB size is say 13KB and I am defining to
use inline store.
What will Oracle use Inline store or out line store?
Request your suggestions and recommendations.
Thanks



Followup September 17, 2004 - 8pm Central time zone:

4000, like a varchar2

My Doubt:
When block size is 16kb and defining inline storage, then if BLOB size is say 13kb, i think it will store inline if BLOB size is less than 16kb.

Full of confusion....
Why row length 4kb limit is applied when we have 4k,8k,16k or 32k block size, i think it should be able to store the string(BLOB or CLOB) inline as block size is greater than string length 4kb.

Could you please help me in clarifying my above doubts in detail.


Tom Kyte
April 12, 2011 - 1:18 pm UTC

it will use out of line storage when it exceeds the size of what a varchar2 could have been. 4000.

Analysis To Determine Optimal CLOB Storage

Randy, June 07, 2011 - 6:41 pm UTC

Tom -

I need to perform analysis of an existing schema with many CLOB columns to determine which, if any, should be stored out-of-line (disable storage in row). I've read the following in the SecureFiles and Large Objects Developer's Guide:

+++++++++++++++++++++++++++++++++++
Performance Guidelines for Small BasicFiles LOBs
If most LOBs in your database tables are small in size—8K bytes or less—and only a few rows have LOBs larger than 8K bytes, then use the following guidelines to maximize database performance:
■ Use ENABLE STORAGE IN ROW.
■ Set the DB_BLOCK_SIZE initialization parameter to 8K bytes and use a chunk size of 8K bytes.
■ See "LOB Storage Parameters" on page 11-4 information on tuning other parameters such as CACHE, PCTVERSION, and CHUNK for the LOB segment.
+++++++++++++++++++++++++++++++++++

I have two questions in regard to the above: 1) For the purposes of my analysis why would I be concerned about LOBS being larger/smaller than 8k when ~4k is the point beyond which the value is always stored out-of-line? It seems like the 4k threshold should enter into the analysis somewhere. 2) When determining if most of the LOB values are small what is a reasonable threshold to use? 80%? I.e. if 80% of the LOBS are smaller than the 8k (or 4k) threshold then is it reasonable to say that most of the LOBS are small (and therefore ENABLE STORAGE IN ROW should be used)?

Thanks in advance...
Randy
Tom Kyte
June 08, 2011 - 10:14 am UTC

... I need to perform analysis of an existing schema with many CLOB columns to determine which, if any, should be stored out-of-line (disable storage in row). I've read the following in the SecureFiles and Large Objects Developer's Guide: ...

No you don't - need to perform an analysis of existing data. You just need to figure out if you want:

inline storage when the clob is less then 4000 bytes

period. If none of your clobs are less then 4000 bytes - the decision is easy:

do nothing, it doesn't matter.

It only matters if some of your clobs are less then 4000 bytes, then the decision primarily comes down to:

do I full scan this table and when full scanning - not select the clob?


If so, then you might have a case for disabling inline storage. Otherwise, you probably really want the reduced resource usage, access times, write times for the inline clobs.




enable storage in row is the default and probably shouldn't be changed.


I would not change my block size (the default is 8k) for a single column in a single schema - that would be like taking an atom bomb to crack a walnut open. (I disagree with their thoughts on considering the block size).


1) I don't know why they picked 8k. It is a bad example, the default block size is already 8k, the default storage is enable storage in row. The only thing that made total sense was the third bullet.

2) see above, You would disable storage in row pretty much only if you a) full scan the table b) when full scanning, you do not select the clob column


huge GC buffer busy waits on Clob column

Balaji, July 14, 2011 - 10:31 pm UTC

Hi Tom

we are noticing huge amount of buffer busy waits on frequently accessed clob field. We are 11.1.0.7 Real application clusters, 8k block size.

The avg size is 8k and chunk size 8k.
We tried several different storage options (basicfile/securefile, cache / no cache/ cache reads disable/enable in row) but still could not eliminate the buffer busywaits on the LOB segments.

Since each clob is stored in single block (or more), not sure how block level contention could happen on this LOB segment.

The clob is inserted/updated/read a lot. First updated with empty_clob() followed by the lob data

update px_promoarg set detail=empty_clob() , OPTCOUNTER=COALESCE(NULLIF(OPTCOUNTER, 32767)+1, 1) where orders_id=:1


Any advise on how to get to the bottom of this?

Thanks
Balaji
Tom Kyte
July 18, 2011 - 8:54 am UTC

if I told you I stopped at 1,000 traffic lights on my last trip - would you be concerned that was a performance issue?

Maybe, maybe not, you'd need more information - such as

a) was this a cross country trip or cross town
b) how long did I spend at these traffic lights, how long did I spend on my trip

for starters.


I don't know what you consider huge (I find often that what some consider huge, I sort of laugh at and say "that's tiny, teeny teeny tiny"

So, in order to get something from us here - you'll need to provide something for us - such as the numbers, the times, the conditions, the methods you used to get the numbers and so on.


Don't forget, a clob is accessed via a LOB INDEX, an index - which if it is updated a lot - will have read consistent images generated and shared between the nodes just like any other index.

GC buffer busy waits on Clob column

A reader, July 19, 2011 - 12:19 am UTC

Here is some details

Testing Method: Load runner executing user transactions (100 virtual users)

Duration: 1 hour 30 minutes

LOB details:
LOB Tablespace index chunk pctversion Cache Securefile
SYS_LOB0000455456C00002$$ LOBDAT01 SYS_IL0000455456C00002$$ 8192 20 No NO


Segments by Global Cache Buffer Busy


Tablespace Name Object Name Obj.Type GC Buffer Busy % of Capture

LOBDAT01 SYS_LOB0000455456C00002$$ LOB 20,893 16.06


Segments by Direct Physical Reads

Tablespace Name Object Name Obj. Type Direct Reads %Total
LOBDAT01 SYS_LOB0000455456C00002$$ LOB 54,943 12.64

Segments by Direct Physical Writes

Tablespace Name Object Name Obj. Type Direct Writes %Total
LOBDAT01 SYS_LOB0000455456C00002$$ LOB 55,790 18.75


Thanks
Bala



Tom Kyte
July 19, 2011 - 7:57 am UTC

like i said, teeny tiny numbers. An average of 3 or 4 per second - events that are measured in single, small digit milliseconds.



I'd be much more worried about your direct reads (almost 3 times as many and 2-3 times longer for each one than a gc wait probably) and your direct writes (almost 3 times as many and probably 4-5 or more times as long...)


Show us the time - number of waits, not relevant. Time spent waiting - priceless.

More to Explore

CLOB/BLOB/etc

Complete documentation on Securefiles and Large Objects here