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]