再上一篇:12.5LONG类型
上一篇:12.6DATE、TIMESTAMP和 INTERVAL类型
主页
下一篇:12.8 ROWID/UROWID类型
再下一篇:12.9小结
文章列表

12.7 LOB类型

Oracle 9i 10g编程艺术:深入数据库体系结构

根据我的经验,LOB或大对象(large object)是产生许多混乱的根源。这些数据类型很容易被误解 , 这包括它们是如何实现的,以及如何最好地加以使用。这一节将概要介绍LOB如何物理地存储,并指出使 用LOB类型是必须考虑哪些问题。这些类型有许多可选的设置,要针对你的应用做出正确的选择,这一点 至关重要。
Oracle中支持4种类型的LOB:
CLOB:字符LOB。这种类型用于存储大量的文本信息,如XML或者只是纯文本。这个数据类 型需要进行字符集转换,也就是说,在获取时,这个字段中的字符会从数据库的字符集转换为 客户的字符集,而在修改时会从客户的字符集转换为数据库的字符集。
NCLOB:这是另一种类型的字符LOB。存储在这一列中的数据所采用的字符集是数据库的国 家字符集,而不是数据库的默认字符集。
BLOB:二进制LOB。这种类型用于存储大量的二进制信息,如字处理文档,图像和你能想像 到的任何其他数据。它不会执行字符集转换。应用向BLOB中写入什么位和字节,BLOB就会返回 什么为和字节。
BFILE:二进制文件LOB。这与其说是一个数据库存储实体,不如说是一个指针。带BFILE 列的数据库中存储的只是操作系统中某个文件的一个指针。这个文件在数据库之外维护,根本 不是数据库的一部分。BFILE提供了文件内容的只读访问。
讨论LOB时,我会分两节讨论上述各个类型:其中一节讨论存储在数据库中的LOB,这也称为内部LOB, 包括CLOB、BLOB和NCLOB;另一节讨论存储在数据库之外的LOB,或 BFILE类型。我不打算分别讨论CLOB、BLOB 或NCLOB,因为为了从存储来看,还是从选项来看,它们都是一样的。只不过CLOB和NCLOB支持文本信息 ,
而BLOB支持二进制信息。不过不论基类型是什么,所指定的选项(CHUNKSIZE、PCTVERSION等)和要考虑 的问题都是一样的。但由于BFILE 与它们有显著不同,所以我们将单独地讨论这种类型。

12.7.1内部 LOB


从表面看,LOB的语法相当简单,但这只是一种假象。你可以创建有CLOB、BLOB或NCLOB数据类型 列的表,就这么简单。似乎使用这些数据类型就像使用NUMBER、DATE或VARCHAR2类型一样容易:
ops$tkyte@ORA10G> create table t
2 ( id int primary key,
3 txt clob
4 )
5 /
Table created.
还能怎么样呢?但这个小例子只是显示出冰山一角,关于LOB能指定的选项很多,这里只是其中极少
的一部分。通过使用DBMS_METADATA方能得到全貌:
ops$tkyte@ORA10G> select dbms_metadata.get_ddl( 'TABLE', 'T' )
2 from dual;

DBMS_METADATA.GET_DDL('TABLE','T')

------------------------------------------------------------------------------- CREATE TABLE "OPS$TKYTE"."T"

( "ID" NUMBER(*,0), "TXT" CLOB,

PRIMARY KEY ("ID")

USING INDEX PCTFREE 12. INITRANS 2 MAXTRANS 255

STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645

PCTINCREASE 0 FREELISTS 12.FREELIST GROUPS 12.BUFFER_POOL DEFAULT) TABLESPACE "USERS" ENABLE

) PCTFREE 12. PCTUSED 40 INITRANS 12.MAXTRANS 255 NOCOMPRESS LOGGING STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645

PCTINCREASE 0 FREELISTS 12.FREELIST GROUPS 12.BUFFER_POOL DEFAULT) TABLESPACE "USERS"

LOB ("TXT") STORE AS (

TABLESPACE "USERS" ENABLE STORAGE IN ROW CHUNK 8192 PCTVERSION 12. NOCACHE

STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 12.MAXEXTENTS 2147483645

PCTINCREASE 0 FREELISTS 12.FREELIST GROUPS 12.BUFFER_POOL DEFAULT))


LOB显然有以下属性:
一个表空间(这个例子中即为USERS) ENABLE STORAGE IN ROW作为一个默认属性 CHUNK 8192
PCTVERSION 12. NOCACHE
一个完整的STORAGE子句
由此说明,在底层 LOB 并不那么简单,而事实上确实如此。LOB 列总是会带来一种多段对象

(multisegment object,这是我对它的叫法),也就是说,这个表会使用多个物理段。如果我们在一个空 模式中创建这个表,就会发现以下结果:
ops$tkyte@ORA10G> select segment_name, segment_type
2 from user_segments;
SEGMENT_NAME SEGMENT_TYPE
------------------------------ ------------------ SYS_C0011927 INDEX SYS_IL0000071432C00002$$ LOBINDEX
SYS_LOB0000071432C00002$$ LOBSEGMENT
T TABLE

这里创建了一个索引来支持主键约束,这很正常,但是另外两个段呢?即lobindex和lobsegment, 它们做什么用?创建这些段是为了支持我们的LOB列。我们的实际LOB数据就存储在lobsegment中(确实 , LOB数据也有可能存储在表T 中,不过稍后讨论ENABLE STORAGE IN ROW子句时还会更详细地说明这个内 容)。lobindex用于执行LOB的导航,来找出其中的某些部分。创建一个LOB列时,一般来说,存储在行 中的这是一个指针(pointer),或 LOB定位器(LOB locator)。我们的应用所获取的就是这个LOB定位器。 当请求得到LOB的“12.000~2,000字节”时,将对lobindex使用LOB定位器来找出这些字节存储在哪里 , 然后再访问lobsegment。可以用lobindex很容易地找到LOB的各个部分。由此说来,可以把LOB想成是 一种主/明细关系。LOB按“块”(chunk)或(piece)来存储,每个片段都可以访问。例如,如果我们使 用表来实现一个LOB,可以如下做到这一点:
Create table parent
( id int primary key, other-data...
);
Create table lob
( id references parent on delete cascade, chunk_number int,
data <datatype>(n),
primary key (id,chunk_number)
);
从概念上讲,LOB的存储与之非常相似,创建这两个表时,在LOB表的ID.CHUNK_NUMBER上要有一个
主键(这对应于Oracle创建的lobindex),而且要有一个 LOB表来存储数据块(对应于lobsegment)。LOB 列为我们透明地实现了这种主/明细结构。图12.-3可以更清楚地展示这个思想。
图12.-3 表-lobindex-lobsegment的对于关系
表中的LOB实际上只是指向lobindex,lobindex再指向LOB本身的各个部分。为了得到LOB中的N~M 字节,要对表中的指针(LOB定位器)解除引用,遍历 lobindex结构来找到所需的数据库(chunk),然后 按顺序访问。这使得随机访问LOB的任何部分都能同样迅速,你可以用同样快的速度得到LOB的最前面、 中间或最后面的部分,因为无需再从头开始遍历LOB。
既然已经从概念上了解了LOB如何存储,下面我将逐个介绍前面所列的各个可选设置,并解释它们的 用途以及有什么具体含义。

1. LOB 表空间


从DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS ( TABLESPACE "USERS" ...
这里指定的TABLESPACE是将存储lobsegment和lobindex表空间,这可能与表本身所在的表空间不
同。也就是说,保存LOB数据的表空间可能不同于保存实际表数据的表空间。
为什么考虑为LOB数据使用另外一个表空间(而不用表数据所在的表空间)呢?注意原因与管理和性 能有关。从管理的角度看,LOB数据类型表示一种规模很大的信息。如果表有数百万行,而每行有一个很 大的LOB,那么LOB就会极为庞大。为LOB数据单独使用一个表空间有利于备份和恢复以及空间管理,单 从这一点考虑,将表与LOB数据分离就很有意义。例如,你可能希望LOB数据使用另外一个统一的区段大 小,而不是普通表数据所用的区段大小。
另一个原因则出于I/O性能的考虑。默认情况下,LOB不在缓冲区缓存中进行缓存(有关内容将在后 面再做说明)。因此,默认情况下,对于每个LOB访问,不论是读还是写,都会带来一个物理I/O(从磁盘 直接读,或者向磁盘直接写)。
注意 LOB可能是内联的(inline),或者存储在表中。在这种情况下,LOB数据会被缓存,但是这只 适用于小于4,000字节的LOB。我们将在“IN ROW子句”一节中进一步讨论这种情况。
由于每个访问都是一个物理I/O,所以如果你很清楚在实际中(当用户访问时)有些对象会比大多数 其他对象经历更多的物理I/O,那么将这些对象分离到它们自己的磁盘上就很有意义。
需要说明,lobindex和lobsegment总是会在同一个表空间中。不能将lobindex和lobsegment放在 不同的表空间中。在Oralce的更早版本中,允许为lobindex和lobsegment分别放在单独的表空间中,但 是从 8i Release 3 以后,就不再允许为lobindex和 logsegment指定不同的表空间。实际上,lobindex
的所有存储特征都是从lobsegment继承的,稍后就会看到。

2. IN ROW 子句


前面的DBMS_METADATA返回的CREATE TABLE语句还包括以下内容:
LOB ("TXT") STORE AS (... ENABLE STORAGE IN ROW ...
这控制了LOB数据是否总与表分开存储(存储在 lobsegment中),或是有时可以与表一同存储,而不
用单独放在lobsegment中。如果设置了ENABLE STORAGE IN ROW,而不是 DISABLE STORAGE IN ROW,小 LOB(最多4,000字节)就会像VARCHAR2一样存储在表本身中。只有当LOB超过了4,000字节时,才会“移 出”到lobsegment中。
默认行为是启用行内存储(ENABLE STORAGE IN ROW),而且一般来讲,如果你知道 LOB总是能在表本 身中放下,就应该采用这种默认行为。例如,你的应用可能有一个某种类型的DESCRIPTION字段。这个 DESCRIPTION可以存储0~32KB的数据(或者可能更多,但大多数情况下都少于或等于32KB)。已知很多描 述都很简短,只有几百个字符。如果把它们单独存储,并在每次获取时都通过索引来访问,就会存在很大 的开销,你完全可以将它们内联存储,即放在表本身中,这就能避免单独存储的开销。不仅如此,如果LOB 还能避免获取LOB时所需的物理I/O。

下面通过一个非常简单的例子来看看这种设置的作用。我们将创建包括有两个LOB的表,其中一个 LOB可以在行内存储数据,而另一个LOB禁用了行内存储:
ops$tkyte@ORA10G> create table t
2 ( id int primary key,
3 in_row clob,
4 out_row clob
5 )
6 lob (in_row) store as ( enable storage in row )
7 lob (out_row) store as ( disable storage in row )
8 /
Table created.

在这个表中,我们将插入一些串数据,所有这些串的长度都不超过4,000字节:
ops$tkyte@ORA10G> insert into t
2 select rownum,
3 owner || ' ' || object_name || ' ' || object_type || ' ' ||
status,
4 owner || ' ' || object_name || ' ' || object_type || ' ' || stat

us
5 from all_objects
6 /
48592 rows created.
ops$tkyte@ORA10G> commit; Commit complete.
现在,如果我们想读取每一行,在此使用了DBMS_MONITOR包,并启用了SQL_TRACE,执行这个工作

时,可以看到这两个表获取数据时的性能:
ops$tkyte@ORA10G> declare
2 l_cnt number;
3 l_data varchar2(32765);
4 begin
5 select count(*)
6 into l_cnt
7 from t;
8
9 dbms_monitor.session_trace_enable;
12. for i in 1 .. l_cnt
12. loop
12. select in_row into l_data from t where id = i;
12. select out_row into l_data from t where id = i;
12. end loop;
12. end;
12. /


PL/SQL procedure successfully completed.
查看这个小仿真的TKPROF报告时,结果一目了然:
SELECT IN_ROW FROM T WHERE ID = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
Parse 1 0.00 0.00 0 0
0 0
Execute 48592 2.99 2.78 0 0
0 0
Fetch 48592 12.84 12.80 0 145776
0 48592
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
total 97185 4.83 4.59 0 145776
0 48592
Rows Row Source Operation
------- ---------------------------------------------------
48592 TABLE ACCESS BY INDEX ROWID T (cr=145776 pr=0 pw=0 time=1770453 us)
48592 INDEX UNIQUE SCAN SYS_C0011949 (cr=97184 pr=0 pw=0 time=960814 us)
******************************************************************************** SELECT OUT_ROW FROM T WHERE ID = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
Parse 1 0.00 0.00 0 0
0 0

Execute 48592 2.21 2.12. 0 0
0 0
Fetch 48592 7.33 8.49 48592 291554 0 4859
2
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
total 97185 9.54 12..62 48592 291554 0 48
592
Rows Row Source Operation
------- ---------------------------------------------------
48592 TABLE ACCESS BY INDEX ROWID T (cr=145776 pr=0 pw=0 time=1421463 us)
48592 INDEX UNIQUE SCAN SYS_C0011949 (cr=97184 pr=0 pw=0 time=737992 us) Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- -------
-----
direct path read 48592 0.00
0.25
获取IN_ROW列显著地快得多,而且所占用的资源也远远少于OUT_ROW列。可以看到,它使用了 145,776
次逻辑I/O(查询模式获取),而OUT_ROW列使用的逻辑I/O次数是它的两倍。初看上去,我们不太清楚这 些额外的逻辑I/O是从哪里来的,不过,如果你还记得LOB是如何存储的就会明白,这是对lobindex段的 I/O(为了找到LOB的各个部分)。这些额外的逻辑I/O都针对这个lobindex.
另外,可以看到,对于 OUT_ROW列,获取48,592行会带来48,592次物理I/O,而这会导致同样数目 的“直接路径读”I/O等待。这些都是对非缓存LOB数据的读取。在这种情况下,通过启用LOB数据的缓 存,可以缓解这个问题,但是这样一来,我们又必须确保为此要有足够多的额外的缓冲区缓存。另外,如 果确实有非常大的LOB,我们可能并不希望缓存这些数据。

这种行内/行外存储设置不仅会影响读,还会影响修改。如果我们要用小串更新前100行,并用小串 插入100个新行,再使用同样的技术查看性能,会观察到:
ops$tkyte@ORA10G> create sequence s start with 100000;
Sequence created.

ops$tkyte@ORA10G> declare
2 l_cnt number;
3 l_data varchar2(32765);
4 begin
5 dbms_monitor.session_trace_enable;
6 for i in 1 .. 100
7 loop
8 update t set in_row =
to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') where id = i;
9 update t set out_row =
to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') where id = i;
12. insert into t (id, in_row) values ( s.nextval, 'Hello World' );
12. insert into t (id,out_row) values ( s.nextval, 'Hello World' );
12. end loop;
12. end;
12. /
PL/SQL procedure successfully completed.

在得到的TKPROF报告中可以观察到类似的结果:
UPDATE T SET IN_ROW = TO_CHAR(SYSDATE,'dd-mon-yyyy hh24:mi:ss')
WHERE ID = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------

Parse 1 0.00 0.00 0 0
0 0
Execute 100 0.05 0.02 0 200 202
100
Fetch 0 0.00 0.00 0 0
0 0
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
total 101 0.05 0.02 0 200
202 100
Rows Row Source Operation
------- ---------------------------------------------------
100 UPDATE (cr=200 pr=0 pw=0 time=15338 us)
100 INDEX UNIQUE SCAN SYS_C0011949 (cr=200 pr=0 pw=0 time=2437 us)
******************************************************************************** UPDATE T SET OUT_ROW = TO_CHAR(SYSDATE,'dd-mon-yyyy hh24:mi:ss')
WHERE ID = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
Parse 1 0.00 0.00 0 0
0 0
Execute 100 0.07 0.12. 0 1100 2421
100
Fetch 0 0.00 0.00 0 0
0 0

------- ------ -------- ---------- ---------- ---------- --
-------- ----------
total 101 0.07 0.12. 0 1100
2421 100
Rows Row Source Operation
------- ---------------------------------------------------
100 UPDATE (cr=1100 pr=0 pw=100 time=134959 us)
100 INDEX UNIQUE SCAN SYS_C0011949 (cr=200 pr=0 pw=0 time=2180 us) Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- -------
-----
direct path write 200 0.00
0.00
可以看到,行外LOB的更新占用了更多的资源。它要花一定的时间完成直接路径写(物理I/O),并
执行更多的当前模式获取以及查询模式获取。这些都源于一点,即除了维护表本身外,还必须维护lobindex

和lobsegment。INSERT操作也显示出了同样的差异:
INSERT INTO T (ID, IN_ROW) VALUES ( S.NEXTVAL, 'Hello World' )
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
Parse 1 0.00 0.00 0 0
0 0
Execute 100 0.03 0.02 0 2
316 100
Fetch 0 0.00 0.00 0 0
0 0
------- ------ -------- ---------- ---------- ---------- --
-------- ----------

total 101 0.03 0.02 0 2
316 100
******************************************************************************** INSERT INTO T (ID,OUT_ROW) VALUES ( S.NEXTVAL, 'Hello World' )
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
Parse 1 0.00 0.00 0 0
0 0
Execute 100 0.08 0.12. 0 605 1839
100
Fetch 0 0.00 0.00 0 0
0 0
------- ------ -------- ---------- ---------- ---------- --
-------- ----------
total 101 0.08 0.12. 0 605
1839 100
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- -------
-----
direct path write 200 0.00
0.00
注意读和写使用的I/O都有所增加。总之,由此显示出,如果使用一个 CLOB,而且很多串都能在“行
内”放下(也就是说,小于4,000 字节),那么使用默认的ENABLE STORAGE IN ROW设置就是一个不错的想 法。

3. CHUNK 子句


前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS ( ... CHUNK 8192 ... )
LOB存储在块(chunk)中;指向LOB数据的索引会指向各个数据块。块(chunk)是逻辑上连续的一
组数据库块(block),这也是 LOB的最小分配单元,而通常数据库的最小分配单元是数据库块。CHUNK大 小必须是Oracle块大小的整数倍,只有这样才是合法值。
从两个角度看,选择CHUNK大小时必须当心。首先,每个LOB实例(每个行外存储的LOB值)会占用 至少一个CHUNK。一个CHUNK有一个LOB值使用。如果一个表有100行,而每行有一个包含7KB数据的LOB, 你就会分配100个CHUNK,如果将CHUNK大小设置为32KB,就会分配100个32KB的CHUNK。如果将CHUNK 大小设置为8KB,则(可能)分配100个8KB的CHUNK。关键是,一个CHUNK只能有一个LOB使用(两个 LOB不会使用同一个CHUNK)。如果选择了一个CHUNK大小,但不符合你期望的LOB大小,最后就会浪费大 量的空间。例如,如果表中的LOB平均有7KB,而你使用的CHUNK大小为32KB,对于每个LOB实例你都会 “浪费”大约25KB的空间,另一方面,倘若使用8KB的CHUNK,就能使浪费减至最少。
还需要注意要让每个LOB实例相应的CHUNK数减至最少。前面已经看到了,有一个lobindex用于指 向各个块,块越多,索引就越大。如果有一个4MB的LOB,并使用8KB的CHUNK,你就至少需要512个CHUNK 来存储这个消息。这也说明,至少需要512个lobindex条目指向这些CHUNK。听上去好像没什么,但是你 要记住,对于每个LOB个数的512倍。另外,这还会影响获取性能,因为与读取更少但更大的 CHUNK相比, 现在要花更长的数据来读取和管理许多小CHUNK。我们最终的目标是:使用一个能使“浪费”最少,同时 又能高效存储数据的CHUNK大小。

4. PCTVERSION 子句


前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS ( ... PCTVERSION 12. ... )
这用于控制LOB的读一致性。在前面的几章中,我们已经讨论了读一致性、多版本和undo在其中所
起的作用。但LOB实现读一致性的方式有所不同。lobsegment并不使用undo来记录其修改;而是直接在 lobsegment本身中维护信息的版本。lobindex会像其他段一样生成undo,但是lobsegment 不会。相反, 修改一个LOB时,Oracle 会分配一个新的CHUNK,并且仍保留原来的CHUNK。如果回滚了事务,对LOB索 引所做的修改会回滚,索引将再次指向原来的CHUNK。因此,undo维护会在LOB段本身中执行。修改数据 时,原来的数据库保持不动,此外会创建新数据。
读LOB数据时这也很重要。LOB是读一致的,这与所有其他段一样。如果你在上午9:00获取一个LOB 定位器,你从中获取的LOB数据就是“上午9:00那个时刻的数据”。这就像是你在上午9:00打开了一个游 标(一个结果集)一样,所生成的行就是那个时间点的数据行。与结果集类似,即使别人后来修改了LOB 数据。在此,Oracle会使用lobsegment,并使用logindex的读一致视图来撤销对LOB的修改,从而提取 获取LOB定位器当时的LOB数据。它不会使用logsegment的undo信息,因为根本不会为logsegment本身 生成undo信息。

中):
可以很容易地展示LOB是读一致的,考虑以下这个小表,其中有一个行外LOB(存储在logsegment
ops$tkyte@ORA10G> create table t
2 ( id int primary key,
3 txt clob
4 )
5 lob( txt) store as ( disable storage in row )

6 /
Table created.
ops$tkyte@ORA10G> insert into t values ( 1, 'hello world' );
12.row created.
ops$tkyte@ORA10G> commit; Commit complete.

如果取出LOB定位器,并在这个表上打开一个游标,如下:
ops$tkyte@ORA10G> declare
2 l_clob clob;
3
4 cursor c is select id from t;
5 l_id number;
6 begin
7 select txt into l_clob from t;
8 open c;

然后修改行,并提交:
9
12. update t set id = 2, txt = 'Goodbye';
12. commit;
12.
可以看到,通过使用LOB定位器和打开的游标,会提供“获取LOB定位器或打开游标那个时间点”的

数据:
12. dbms_output.put_line( dbms_lob.substr( l_clob, 100, 1 ) );
12. fetch c into l_id;

12. dbms_output.put_line( 'id = ' || l_id );
12. close c;
12. end;
12. /
hello world id = 1
PL/SQL procedure successfully completed.

但是数据库中的数据很可能已经更新/修改:
ops$tkyte@ORA10G> select * from t;
ID TXT
---------- ---------------
2 Goodbye
游标C的读一致映像来自undo段,而LOB的读一致映像则来自LOB段本身。
由此,我们要考虑这样一个问题:如果不用undo段来存储回滚LOB所需要的信息,而且LOB支持读 一致性,那我们怎么避免发生可怕的ORA-01555:snapshot too old错误呢?还有一点同样重要,如何控制 这些旧版本占用的空间呢?这正是PCTVERSION起作用的地方。
PCTVERSION 控制着用于实现LOB数据版本化的已分配LOB空间的百分比(这些数据库块由某个时间 点的LOB所用,并处在lobsegment的HWM以下)。对于许多使用情况来说,默认设置12.%就足够了,因为 在很多情况下,你只是要INSERT和获取LOB(通常不会执行LOB的更新;LOB往往会插入一次,而获取多 次)。因此,不必为LOB版本化预留太多的空间(甚至可以没有)。

不过,如果你的应用确实经常修改LOB,倘若你频繁地读LOB,与此同时另外某个会话正在修改这些 LOB,12.%可能就太小了。如果处理LOB时遇到一个ORA-22924错误,解决方案不是增加undo表空间的大 小,也不是增加undo保留时间(UNDO_RETENTION),如果你在使用手动 undo管理,那么增加更多RBS空间 也不能解决这个问题。而是应该使用以下命令:
ALTER TABLE tabname MODIFY LOB (lobname) ( PCTVERSION n );
并增加lobsegment中为实现数据版本化所用的空间大小。

5. RETENTION 子句

这个子句与PCTVERSION 子句是互斥的,如何数据库中使用自动undo管理,就可以使用这个子句。 RETENTION子句并非在lobsegment中保留某个百分比的空间来实现LOB的版本化,而是使用基于时间的机 制来保留数据。数据库会设置参数UNDO_RETENTION,指定要把undo信息保留多长时间来保证一致读。在
这种情况下,这个参数也适用于LOB数据。 需要注意,不能使用这个子句来指定保留时间;而要从数据库的UNDO_RETENTION设置来继承它。

6. CACHE 子句


前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS (... NOCACHE ... )
除了NOCACHE,这个选项还可以是CACHE或CACHE READS。这个子句控制了 lobsegment数据是否存储
在缓冲区缓存中。默认的NOCACHE指示,每个访问都是从磁盘的一个直接读,类似地,每个写/修改都是对 大盘的一个直接写。CACHE READS允许缓存从磁盘读的LOB数据,但是LOB数据的写操作必须直接写至磁 盘。CACHE则允许读和写时都能缓存LOB数据。

在许多情况下,默认设置可能对我们并不合适。如果你只有小规模或中等规模的LOB(例如,使用LOB 来存储只有几KB的描述性字段),对其缓存就很有意义。如果不缓存,当用户更新描述字段时,还必须等 待I/O将数据写指磁盘(将执行一个CHUNK大小的I/O,而且用户要等待这个I/O完成)。如果你在执行多 个LOB的加载,那么加载每一行时都必须等待这个I/O完成。所以启用执行LOB缓存很合理。你可以打开 和关闭缓存,来看看会有什么影响:
ALTER TABLE tabname MODIFY LOB (lobname) ( CACHE );
ALTER TABLE tabname MODIFY LOB (lobname) ( NOCACHE );
对于一个规模很多的初始加载,启用LOB的缓存很有意义,这允许DBWR在后台将LOB数据写至磁盘,
而你的客户应用可以继续加载更多的数据。对于频繁访问或修改的小到中等规模的LOB,缓存就很合理, 可以部门让最终用户实时等待物理I/O完成。不过,对于一个大小为50MB的LOB,把它放在缓存中就没带 道理了。
要记住,此时可以充分使用Keep池或回收池。并非在默认缓存中将lobsegment数据与所有“常规” 数据一同缓存,可以使用保持池或回收池将其分开缓存。采用这种方式,既能缓存LOB数据,而且不影响 系统中现有数据的缓存。

7. LOB STORAGE 子句


最后,前面的DBMS_METADATA返回的CREATE TABLE语句还包括以下内容:
LOB ("TXT") STORE AS ( ... STORAGE(INITIAL 65536 NEXT 1048576
MINEXTENTS 12.MAXEXTENTS 2147483645 PCTINCREASE 0 FREELISTS 1
FREELIST GROUPS 12.BUFFER_POOL DEFAULT ) ... )
也就是说,它有一个完整的存储子句,可以用来控制物理存储特征。需要指出,这个存储子句同样适
用于lobsegment和lobindex,对一个段的设置也可以用于另一个段。假设有一个本地管理的表空间,LOB 的相关设置将是 FREELISTS、FREELIST GROUPS 和 BUFFER_POOL。我们在第 12.章讨论过 FREELISTS 和 FREELIST GROUPS与表段的关系。这些讨论同样适用于lobindex段,因为lobindex与其他索引段的管理 是一样的。如果需要高度并发地修改LOB,可能最好在索引段上设置多个FREELISTS。
上一节已经提到,对LOB段使用保持池或回收池可能是一个很有用的技术,这样就能缓存LOB数据, 而且不会“破坏”现有的默认缓冲区缓存。并不是将LOB与常规表一同放在块缓冲区中,可以在SGA中专 门为这些LOB对象预留一段专用的内存。BUFFER_POOL子句可以达到这个目的。

12.7.2 BFILE

我们要讨论的最后一种LOB类型是BFILE类型。BFILE类型只是操作系统上一个文件的指针。它用于 为这些操作系统文件提供只读访问。
注意 内置包UTL_FILE也为操作系统文件提供了读写访问。不过它没有使用BFILE类型。

使用BFILE 时,还有使用一个Oracle DIRECTORY对象。DIRECTORY对象只是将一个操作系统目录映 射至数据库中的一个“串”或一个名称(以提供可移植性;你可能想使用BFILE 中的一个串,而不是操作 系统特定的文件名约定)。作为一个小例子,下面创建一个带BFILE列的表,并创建一个DIRECTORY对象, 再插入一行,其中引用了文件系统中的一个文件:
ops$tkyte@ORA10G> create table t
2 ( id int primary key,
3 os_file bfile
4 )
5 /
Table created.
ops$tkyte@ORA10G> create or replace directory my_dir as '/tmp/'
2 / Directory created.
ops$tkyte@ORA10G> insert into t values ( 1, bfilename( 'MY_DIR', 'test.dbf' ) );
12.row created.

现在,就可以把BFILE当成一个LOB来处理,因为它就是一个LOB。例如,我们可以做下面的工作:
ops$tkyte@ORA10G> select dbms_lob.getlength(os_file) from t;
DBMS_LOB.GETLENGTH(OS_FILE)
---------------------------
1056768

可以看到所指定的文件大小为1MB。注意,这里故意在INSERT语句中使用了MY_DIR。如果使用混合 大小写或小写,会得到以下错误:
ops$tkyte@ORA10G> update t set os_file = bfilename( 'my_dir', 'test.dbf' );
12.row updated.
ops$tkyte@ORA10G> select dbms_lob.getlength(os_file) from t;
select dbms_lob.getlength(os_file) from t
*
ERROR at line 1:
ORA-22285: non-existent directory or file for GETLENGTH operation
ORA-06512: at "SYS.DBMS_LOB", line 566
这个例子只是说明:Oracle中的DIRECTORY对象是标识符,而默认情况下标识符都以大写形式存储。

BFILENAME内置函数接受一个串,这个串的大小写必须与数据字典中存储的DIRECTORY对象的大小写完全 匹配。所以,我们必须在BFILENAME函数中使用大写,或者在创建DIRECTORY对象时使用加引号的标识符:
ops$tkyte@ORA10G> create or replace directory "my_dir" as '/tmp/'
2 /
Directory created.
ops$tkyte@ORA10G> select dbms_lob.getlength(os_file) from t;
DBMS_LOB.GETLENGTH(OS_FILE)
---------------------------
1056768
我不建议使用加引号的标识符;而倾向于在BFILENAME调用中使用大写。加引号的标识符属于“异类”,
可能会在以后导致混淆。
BFILE 在磁盘上占用的空间不定,这取决于DIRECTORY对象名的文件名的长度。在前面的例子中,所 得到的BFILE长度大约为35字节。一般来说,BFILE会占用大约20字节的开销,再加上DIRECTORY对象 的长度以及文件名本身的长度。
与其他LOB数据不同,BFILE数据不是“读一致”的。由于BFILE在数据库之外管理,对BFILE解除 引用时,不论文件上发生了什么,都会反映到你得到的结果中。所以,如果反复读同一个BFILE,可能会 产生不同的结果,这与对CLOB、BLOB或NCLOB使用LOB定位器不同。