1.LOB对象的分类
   Oracle 支持多种类型的大对象,用于存储非结构化的数据,如图片、文档等内容。应用最为广泛的是LOB 类型,LOB 有4 种类型:
   --CLOB  字符大对象,用于替换较老的LONG 类型;
   --BLOB  二进制大对象,主要用于存储二进制格式的大对象
   --NCLOB  基于国家语言字符集的字符大对象
   --BFILE  大对象存储于数据库外部的操作系统文件系统中
在ZLHIS 及ZLBH 中都大量应用了LOB 类型来保存诸如,电子病历、序列化对象、检查图像结果等信息; 文本讨论的内容不包括BFILE 类型。
 
2. LOB 对象的存储结构
  如果表中存储在lob 对象,将会创建两个新增的物理段结构:
  --LOBINDEX , LOB 索引用于快速定位LOB 段中的LOB 对象
  --LOBSEGMENT ,LOB 段
   可以通过DBA_LOBS 、ALL_LOBS 、USER_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  ,真实的二进制数据;
  三种结构示意如下:    
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 row 和disable storage in row 来进行控制,这个选项只能在表创建时指定, 存储在行内的语法如下:
    STORE AS ( enable storage in row )
     如果LOB 数据小于4000 字节,ORACLE 将LOB 存储于与表相同的段内; 实际上,最大的存储在行内的LOB 大小是3964 字节。如果LOB 数据大于3964 字节将存储到独立的LOB 段上(也就是存储在行外了)。
    如果LOB 对象大于3964 字节,且设置了”enable storage in row” ,则LOB 列中在38 到84 字节处存储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 包括了INODE 与LOCATOR ,这样就有了LOB 段中的数据块的映射关系,通过存储在行内的LOCATOR 与LOB INDEX 来快速检索特定的LOB 数据。
    存储在行外的 LOB 数据最小的段分配单位是数据库块( DB_BLOCK_SIZE ),比如 8K ;这样即使你的 LOB 对象很小,也将至少占用一个数据块;如果你的 chink size 设置为大于块的 1 倍大小,比如 10K, 一个 LOB 数据将占用至少 2 个数据块。
    存储在行内的 LOB( 注意即使启用了存储在行内的特性,要存储在行内也必须是小于 4000 字节的 LOB 对象 ) ,在事务中仅仅产生行中存储的定位器 (LOCATOR) 与 LOB INDEX 的 UNDO 信息,相比存储在行外的 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 SIZE 为 8k ,也就是 1 倍数据块的大小。
     SQL> select chunk
     2 from dba_lobs
     3 where table_name='TEST_LOB'
     4 and owner=USER
     5 ;
      CHUNK
     ----------
      8192
 
    需要注意, CHUNK 仅仅对存储在行外的 LOB 数据有效。 从上面可以看出, CHUNK 或 DB_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 数据与结构化数据的缓存机制也不一样,这需要引起我们的注意,与普通的段一样,也可以指定CACHE 与NOCACHE 选项:
   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 个字节,也将会产生所有 32K 的 redo 记录,如果是 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]