Oracle 10g小Blob或Clob没有内联存储?

时间:2011-09-09 12:36:31

标签: performance oracle blob inline clob

根据我读过的文件,CLOB或BLOB的默认存储是内联的,这意味着如果它的大小小于约4k,那么它将保存在表中。

但是当我在Oracle(10.2.0.1.0)中的虚拟表上测试它时,Oracle Monitor(由Allround Automations)的性能和响应表明它已经被表格保留了。

这是我的测试场景......

create table clobtest ( x int primary key, y clob, z varchar(100) )  
;
insert into clobtest 
   select object_id, object_name, object_name  
   from all_objects where rownum < 10001  
;
select COLUMN_NAME, IN_ROW 
from user_lobs 
where table_name = 'CLOBTEST'  
;

这显示:是(建议Oracle将clob存储在行中)

select x, y from CLOBTEST where ROWNUM < 1001 -- 8.49 seconds  
select x, z from CLOBTEST where ROWNUM < 1001 -- 0.298 seconds  

因此,在这种情况下,CLOB值的最大长度为30个字符,因此应始终为内联。如果我运行Oracle Monitor,它会显示一个LOB.Length,后面跟着返回的每一行的LOB.Read(),再次表明clob值是用表保存的。

我也试过像这样创建表格

create table clobtest 
    ( x int primary key, y clob, z varchar(100) ) 
    LOB (y) STORE AS     (ENABLE STORAGE IN ROW)  

但得到了完全相同的结果。

有没有人有任何建议我如何强制(说服,鼓励)Oracle将clob值存储在表中? (我希望获得与读取varchar2列z相似的响应时间)

更新:如果我运行此SQL

select COLUMN_NAME, IN_ROW, l.SEGMENT_NAME, SEGMENT_TYPE, BYTES, BLOCKS, EXTENTS 
from user_lobs l 
      JOIN USER_SEGMENTS s
       on (l.Segment_Name = s. segment_name )
where table_name = 'CLOBTEST'  

然后我得到以下结果......

Y   YES SYS_LOB0000398621C00002$$   LOBSEGMENT  65536   8   1  

5 个答案:

答案 0 :(得分:9)

Oracle LOB的行为如下。

LOB在以下情况下内联存储:

(
  The size is lower or equal than 3964
  AND
  ENABLE STORAGE IN ROW has been defined in the LOB storage clause
) OR (
  The value is NULL
)

在以下情况下,LOB存储在行外:

(
  The value is not NULL
) AND (
  Its size is higher than 3964
  OR
  DISABLE STORAGE IN ROW has been defined in the LOB storage clause
)

现在这不是影响性能的唯一问题。

如果LOB最终没有内联存储,则Oracle的默认行为是避免缓存它们(只有内联LOB缓存在缓冲区缓存中,并与该行的其他字段一起缓存)。要告诉Oracle还要缓存非内联LOB,在定义LOB时应使用CACHE选项。

默认行为是ENABLE STORAGE IN ROW和NOCACHE,这意味着小型LOB将被内联,大型LOB将不会(并且不会被缓存)。

最后,通信协议级别也存在性能问题。典型的Oracle客户端将为每个LOB执行2次额外的往返以获取它们:    - 一个用于检索LOB的大小并相应地分配内存    - 一个用于获取数据本身(假设LOB很小)

即使使用数组接口检索结果,也会执行这些额外的往返。如果检索1000行并且数组大小足够大,则需要支付1次往返来检索行,并支付2000次往返以检索LOB的内容。

请注意它取决于LOB是否内联存储的事实。他们是完全不同的问题。

为了在协议级别进行优化,Oracle提供了一个新的OCI动词,用于在一次往返中获取多个LOB(OCILobArrayRead)。我不知道JDBC是否存在类似的东西。

另一个选择是在客户端绑定LOB,就好像它是一个大的RAW / VARCHAR2。这仅在可以定义LOB的最大大小时才起作用(因为必须在绑定时提供最大大小)。这个技巧避免了额外的rountrips:LOB只是像RAW或VARCHAR2一样处理。我们在LOB密集型应用程序中大量使用它。

一旦优化了往返次数,就可以在网络配置中调整数据包大小(SDU)的大小以更好地适应这种情况(即有限数量的大型往返)。它倾向于减少“SQL * Net更多数据到客户端”和“SQL * Net更多来自客户端的数据”等待事件。

答案 1 :(得分:1)

如果您“希望获得与读取varchar2列z相似的响应时间”,那么在大多数情况下您会感到失望。 如果您正在使用CLOB,我认为您需要存储超过4,000个字节,对吧?然后,如果你需要读取更多需要更长时间的字节。

但是如果你有一个肯定的情况,你使用CLOB,但你只对列的前4,000个字节(或更少)感兴趣(在某些情况下),那么你有可能获得类似的性能。 如果您在表中使用类似DBMS_LOB.SUBSTR和ENABLE STORAGE IN ROW CACHE子句的内容,Oracle看起来可以优化检索。例如:

CREATE TABLE clobtest (x INT PRIMARY KEY, y CLOB)
LOB (y) STORE AS (ENABLE STORAGE IN ROW CACHE);

INSERT INTO clobtest VALUES (0, RPAD('a', 4000, 'a'));
UPDATE clobtest SET y = y || y || y;
INSERT INTO clobtest SELECT rownum, y FROM all_objects, clobtest WHERE rownum < 1000;

CREATE TABLE clobtest2 (x INT PRIMARY KEY, z VARCHAR2(4000));

INSERT INTO clobtest2 VALUES (0, RPAD('a', 4000, 'a'));
INSERT INTO clobtest2 SELECT rownum, z FROM all_objects, clobtest2 WHERE rownum < 1000;

COMMIT;

在我对10.2.0.4和8K块的测试中,这两个查询的性能非常相似:

SELECT x, DBMS_LOB.SUBSTR(y, 4000) FROM clobtest;
SELECT x, z FROM clobtest2;

来自SQL * Plus的示例(我多次运行查询以删除物理IO):

SQL> SET AUTOTRACE TRACEONLY STATISTICS
SQL> SET TIMING ON
SQL>
SQL> SELECT x, y FROM clobtest;

1000 rows selected.

Elapsed: 00:00:02.96

Statistics
------------------------------------------------------
          0  recursive calls
          0  db block gets
       3008  consistent gets
          0  physical reads
          0  redo size
     559241  bytes sent via SQL*Net to client
     180350  bytes received via SQL*Net from client
       2002  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL> SELECT x, DBMS_LOB.SUBSTR(y, 4000) FROM clobtest;

1000 rows selected.

Elapsed: 00:00:00.32

Statistics
------------------------------------------------------
          0  recursive calls
          0  db block gets
       2082  consistent gets
          0  physical reads
          0  redo size
      18993  bytes sent via SQL*Net to client
       1076  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed

SQL> SELECT x, z FROM clobtest2;

1000 rows selected.

Elapsed: 00:00:00.18

Statistics
------------------------------------------------------
          0  recursive calls
          0  db block gets
       1005  consistent gets
          0  physical reads
          0  redo size
      18971  bytes sent via SQL*Net to client
       1076  bytes received via SQL*Net from client
         68  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1000  rows processed

正如您所看到的,一致的获取相当高,但SQL * Net往返和字节在后两个查询中几乎相同,这显然会对执行时间产生很大影响!

虽然有一个警告:如果你有大量的结果集,一致性获取的差异可能会成为更可能的性能问题,因为你将无法将所有内容保存在缓冲区缓存中,并且最终会导致非常昂贵的物理读取...

祝你好运!

干杯

答案 2 :(得分:0)

CLOB和BLOB有两个方向:

  1. LOB值可能存储在与行的其余部分不同的数据库段中。

  2. 当您查询该行时,结果集中只包含非LOB字段,并且访问LOB字段需要在客户端和服务器之间进行一次或多次额外的往返(每行!)。 / p>

  3. 我不太清楚你是如何测量执行时间的,我从来没有使用过Oracle Monitor,但你可能主要受第二个间接影响。根据您使用的客户端软件,可以减少往返次数。例如。当您使用ODP.NET时,该参数称为 InitialLobFetchSize

    <强>更新

    要知道两个间接中的哪一个是相关的,您可以使用1000行两次运行LOB查询。如果时间从第一次运行到第二次运行显着下降,则它是间接的1.在第二次运行时,缓存得到回报,并且对单独的数据库段的访问不再是非常相关的。如果时间保持不变,那就是第二个间接,即客户端和服务器之间的往返,这在两次运行之间无法改善。

    在一个非常简单的查询中,1000行超过8秒的时间表示它是间接2,因为1000行的8秒无法用磁盘访问来解释,除非您的数据非常分散且磁盘系统负载很重

答案 3 :(得分:0)

实际上,它存储在行中。您可能正在处理使用LOB而不是varchar的简单开销。没有什么是免费的。 DB可能不知道提前找到行的位置,所以它可能仍然“跟随指针”并且在LOB很大的情况下做额外的工作。如果你可以使用varchar,你应该。即使像2个varchars这样处理8000个字符的老式黑客也可能以更高的性能解决您的业务案例。

LOBS很慢,难以查询等。积极的,它们可以是4G。

尝试的有趣之处是将超过4000个字节的内容推入该clob中,并查看性能的样子。也许它的速度大致相同?这会告诉你,这会降低你的速度。

警告,在某些时候,到您的PC的网络流量会减慢您的速度。

通过包装计数来最小化,这会将工作隔离到服务器:

选择count(*)from(选择x,y来自clobtest,其中rownum&lt; 1001)

您可以通过“set autot trace”实现类似的效果,但也会产生跟踪开销。

答案 4 :(得分:0)

这是关键信息(如何在没有额外往返的情况下读取LOB),这在我认为的Oracle文档中是不可用的:

  

另一个选择是在客户端绑定LOB,就好像它是一个大的   RAW / VARCHAR2。这仅适用于LOB的最大大小   已定义(因为必须在绑定时提供最大大小)。这个   技巧避免额外的rountrips:LOB只是像RAW一样处理   或VARCHAR2。我们在LOB密集型应用程序中大量使用它。

我在加载一个blob列(14KB =&gt;数千行)的简单表(几GB)时出现问题,我正在调查它很长一段时间,尝试了很多lob存储调整(DB_BLOCK_SIZE用于新的表空间, lob存储规范 - CHUNK),sqlnet.ora设置,客户端预取属性,但是这个(在客户端使用OCCI ResultSet-&gt; setBufferData将BLOB视为LONG RAW)是最重要的事情(说服oracle立即发送blob列而不发送首先是吊耳定位器,然后根据吊环定位器分别加载每个吊钩。

现在我可以获得~500Mb / s的吞吐量(列数<3964B)。 我们的14KB blob将被分成多个列 - 因此它将被存储在行中以从HDD获得几乎顺序的读取。使用一个14KB的blob(一列),由于非顺序读取(iostat:少量的合并读取请求),我得到~150Mbit / s。

注意:不要忘记设置lob预取大小/长度:

err = OCIAttrSet(session,(ub4)OCI_HTYPE_SESSION,(void *)&amp; default_lobprefetch_size,0,(ub4) OCI_ATTR_DEFAULT_LOBPREFETCH_SIZE ,errhp);

但我不知道如何使用ODBC连接器实现相同的获取吞吐量。我没有成功地尝试它。