1.LOB对象的分类
   Oracle支持多种类型的大对象,用于存储非结构化的数据,如图片、文档等内容。应用最为广泛的是LOB类型,LOB4种类型:
   --CLOB 字符大对象,用于替换较老的LONG类型;
   --BLOB 二进制大对象,主要用于存储二进制格式的大对象
   --NCLOB  基于国家语言字符集的字符大对象
   --BFILE 大对象存储于数据库外部的操作系统文件系统中
ZLHISZLBH中都大量应用了LOB类型来保存诸如,电子病历、序列化对象、检查图像结果等信息;文本讨论的内容不包括BFILE类型。
 
2. LOB对象的存储结构
  如果表中存储在lob对象,将会创建两个新增的物理段结构:
  --LOBINDEX , LOB索引用于快速定位LOB段中的LOB对象
  --LOBSEGMENTLOB
   可以通过DBA_LOBSALL_LOBSUSER_LOBS视图来查看LOB段的信息,例如:
 SQL> select column_name,segment_name,index_name
 2 from dba_lobs
 3 where table_name='病历标记图形'
 4 and owner='ZLHIS'
 5 /
COLUMN_NAME          SEGMENT_NAME                   INDEX_NAME
-------------------- ------------------------------ -------------------
图形                 SYS_LOB0000052398C00004$$      SYS_IL0000052398C00004$$
 
上述*_LOBS视图提供了下列的列:
OWNER              表所有者
TABLE_NAME         表名称
COLUMN_NAME       LOB列的名称
SEGMENT_NAME      LOB段名称
INDEX_NAME         LOB Index的名称
CHUNK              颗粒大小(单位为字节)
PCTVERSION          PctVersion参数(后面有详细介绍)
CACHE               LOB段是否开启CACHE (yes/no)
LOGGING             Logging选项       (yes/no)
IN_ROW             是否开启在行内存储选项       (yes/no)
 
   在内部,Oracle通过一个叫做定位器的指针来定位特定行对应的LOB对象,在LOB中有三种不同类型的结构:
   --Lob locator ,占用20个字节,存储于表段中;
   --Lob Inode ,最少16字节,存储于LOB Index段中
   --Data array ,真实的二进制数据;
  三种结构示意如下:   Oracle Lob存储及性能问题探讨_Oracle lob 性能 参数
3. LOB存储参数
3.1 表空间
   LOB对象存储在那一个表空间中,取决于你在Lob对象中的storage子句设置:
   --如果没有为LOB指定表空间,则LOB段与LOBIndex都存储到与表相同的表空间上;
   --如果指定了表空间,则LOB段与LobIndex都存储到指定的表空间上;
 为LOB类型指定存储表空间的例子如下:
   create table ZLHIS.病历标记图形
  (
    编码 VARCHAR2(4) not null,
    名称 VARCHAR2(30),
    简码 VARCHAR2(10),
    图形 BLOB )
    LOB(图形)
    STORE AS 病历标记图形_图形_LOB (
    TABLESPACE zl9eprlob
    pctversion 10 disable storage in row chunk 4k
    INDEX 病历标记图形_图形_LOB_INDEX (
    TABLESPACE zl9eprlob)  )
 也可以为LOB段与LOB Index指定名称,如果没有指定则由Oracle自动生成一个唯一名称。
 
3.2  存储在行内还是行外
   LOB是否存储在行内,通过enalbe storage in rowdisable storage in row 来进行控制,这个选项只能在表创建时指定,存储在行内的语法如下:
   STORE AS ( enable storage in row )
    如果LOB数据小于4000字节,ORACLELOB存储于与表相同的段内;实际上,最大的存储在行内的LOB大小是3964字节。如果LOB数据大于3964字节将存储到独立的LOB段上(也就是存储在行外了)。
    如果LOB对象大于3964字节,且设置了”enable storage in row”,则LOB列中在3884字节处存储LOB对象的控制数据。同时需要注意的是,行内存储较大的LOB对象,可能造成行迁移现象;比如,存储3900字节的LOB对象到2k的数据块里,这一行将存储在2个或更多的块上;如果有大量的lob存在这种现象,可能极大的影响LOB的读写性能。
    同样,如果存储在行内,LOB数据与标准的结构化数据在redo/undo机制的上是完全相同的,关于这个内容后面还会有说明。
    存储在行外的语法如下
    STORE AS ( disable storage in row )
    这个选项将所有的LOB数据存储到独立的LOB段上,而不论LOB对象的大小。20个字节的定位器(Locator)指针存储在表的行里,唯一标识存储在LOB段中的LOB数据。LOB INDEX包括了INODELOCATOR,这样就有了LOB段中的数据块的映射关系,通过存储在行内的LOCATORLOB INDEX来快速检索特定的LOB数据。
    存储在行外的LOB数据最小的段分配单位是数据库块(DB_BLOCK_SIZE),比如8K;这样即使你的 LOB对象很小,也将至少占用一个数据块;如果你的chink size设置为大于块的1倍大小,比如10K,一个LOB数据将占用至少2个数据块。
    存储在行内的LOB(注意即使启用了存储在行内的特性,要存储在行内也必须是小于4000字节的LOB对象),在事务中仅仅产生行中存储的定位器(LOCATOR)LOB INDEXUNDO信息,相比存储在行外的LOB对象就少了许多。存储在行外的LOB数据的存取,则使用直接路径的方式进行读取,也就是绕过数据库缓存来进行读取。  
 
3.3 颗粒大小(CHUNK )
   指定CHUNK大小的语法如下:
   STORE AS ( CHUNK bytes )
   CHUNK只能在创建时指定。CHUNK的单位为字节,只能是DB_BLOCK_SIZE的倍数;也就是说最小为数据块的大小,Oracle将自动取一个最接近DB_BLOCK_SIZE倍数的大小。例如, db_block_size设置为2k的情况下,将chunk设置为3000字节,chunk将自动设置为4096字节。通过一个实验来说明一下:
 
   --建表时,将CHUNK指定为3000字节
   SQL> create table TEST_LOB
   2 (
   3    IMAGE BLOB
   4 )
   5   LOB(IMAGE)
   6    STORE AS TEST_IMAGE_LOB (
   7      TABLESPACE zl9eprlob
   8      pctversion 10 disable storage in row chunk 3000
   9      INDEX TEST_IMAGE_INDEX (
   10      TABLESPACE zl9eprlob)
   11    );
   Table created
   --查询LOB视图,看到Oracle自动设置CHINK SIZE8k,也就是1倍数据块的大小。
    SQL> select chunk
     2 from dba_lobs
     3 where table_name='TEST_LOB'
     4 and owner=USER
     5 ;
      CHUNK
     ----------
      8192
 
    需要注意,CHUNK仅仅对存储在行外的LOB数据有效。从上面可以看出,CHUNKDB_BLOCK_SIZE确定了存储在行外的LOB数据的空间分配单位。例如,CHUNK设置为32K,且设置了”disable storage in row”,即使LOB数据只有10字节大小,也将在LOB段中分配32K的空间。如果chunk设置不合理,就可能造成空间的巨大浪费,也会对性能造成不好的影响。
 
3.4  PCTVERSION
    我们都知道Oracle中有“一致性读”的概念,事务提交前的“前镜像数据”会存储到UNDO表空间中,如果查询的开始时间晚于事务的开始时间,将从UNDO表空间读取修改前的数据,这特性被称为“一致性读(Consistent Read)”。前面说过对存储在行外的LOB对象,LOB数据本身是不生产UNDO信息的,那Oracle如何保证LOB对象的读一致性呢?Oracle为解决这个问题,专门引入了PCTVERSION参数,语法如下:
    STORE AS ( PCTVERSION n )
    这个参数可以在创建之后进行修改,PCTVERSION用于控制LOB保存前镜像数据存储空间的百分比,保存的这些前镜像数据就用于LOB对象的一致性读;这些数据保存在与LOB段相同的表空间中,如果设置太大将使得表空间膨胀得很快。
   如果一个会话尝试读取一个被覆盖的LOB前镜像数据(原因就是pct version设置得太小),同样将产生”ora-01555:快照太旧的错误。需要说明的是pctversion只是一个大概值,Oracle内部有一个特殊的算法来计算保留的空间。如果系统中有针对LOB的大量的长时间事务,就需要结合各种情况,设置一个合理的值。
 
3.5 LOB数据的缓存
   Oracle使用数据库调整缓存来加快数据的存取速度,LOB数据与结构化数据的缓存机制也不一样,这需要引起我们的注意,与普通的段一样,也可以指定CACHENOCACHE选项:
   STORE AS ( CACHE ) /  STORE AS ( NOCACHE )
  这个选项也可以在创建表之后通过alter语句进行修改。缺省设置为NOCACHE,在NOCACHE下,读写LOB数据将使用直接路径读写,绕过数据库高速缓存,直接从磁盘进行读取;这意味着Oracle将不缓存LOB段的数据块;如果在AWR/STATSPACK中发现direct path read/ direct path wirte等待很高,就可能是LOB对象的读写引起的。
   设置为CACHE,则LOB的读写则将通过数据库调整缓存进行;这种方式下读取LOB的等待事件显示为"db file sequential read",但是不象扫描表的数据块那样置于LRU列表的“最近使用”端的末尾。
    设置为cache选项一定要非常小心,如果表中的LOB段很大,缓存这些LOB段就需要消耗大量的内存;如果表的lob段总的空间不大,且读写频繁,设置为cache就比较恰当。例如,我们在实际调优过程中,将ZLBH资源信息的RESOURCEINFO表中的序列化窗体的LOB列设置为cache就取得了比较好的效果。
     存储在行内的LOB列是不受CACHE/NOCACHE选项影响的,存储在行内的LOB数据同普通数据块一样,通过数据库高速缓存进行读写。CACHE/NOCACHE选项也会对REDO日志的数量产生影响。NOCACHE时,直接路径读写LOB对象,整个LOB块映象会写入REDO;而设置CACHE,仅仅LOB数据块变化时,才会写入到REDO中。例如,设置了'DISABLE STORAGE IN ROW NOCACHE CHUNK 32K'的情况下,即使LOB数据仅仅只有5个字节,也将会产生所有32Kredo记录,如果是cache选项,则只产生5字节的redo记录。
 
3.6 LOGGING
    我们知道Oracle在表、索引等数据段上,可以使用nologging选项,来减少REDO产生的量,以达到提升性能的目的。在LOB类型中,这个选项必须依赖于NOCACHE选项,只有下面两种组合:
STORE AS ( NOCACHE  LOGGING )
STORE AS ( NOCACHE  NOLOGGING )
   也就是说在CACHE选项下,Oracle是强制使用LOGGING方式的;这个值缺省为 LOGGING。如果启用了NOLOGGING,当发生数据损坏,Oracle需要做介质恢复时,LOB段将被标记为损坏,也就是损失的LOB数据是无法恢复的,原因就是没有产生恢复必须的REDO日志。  
 
3.7 结语
    如果更新存储在行外的LOB数据,将会创建一个版本的LOB前镜像数据,这些数据是要消耗一定的存储空间,前已有所述,这是为了实现“一致性读”的需要,也就是说LOB段需要消耗比结构化数据更多的空间,才能实现事务的并发。同时在开发时,我们可以结合 LOB对象的CHUNK大小,设置数据库驱动中一次读取LOB对象的缓存大小。
   在不同的应用场景,需要分析我们的应用并结合LOB平均大小、数据块大小、Oracle的内存设置等因素来综合考虑我们的LOB设置。
   希望本文能给你正确使用LOB类型带来帮助!
参考资料:
  Metalink: Oracle8 Example SQL Demonstrating use of LOBs in Oracle8 [ID 47740.1]
  Metalink: LOBs and ORA-01555 troubleshooting [ID 846079.1]