Oracle中支持4种类型的LOB:
CLOB:字符LOB。存储大量的文本信息,如XML或者只是纯文本。这个数据类型需要进行字符集转换,也就是说,在获取时,这个字段中的字符会从数据库的字符集转换为客户的字符集,而在修改时会从客户的字符集转换为数据库的字符集。
NCLOB:这是另一种类型的字符LOB。存储在这一列中的数据所采用的字符集是数据库的国家字符集,而不是数据库的默认字符集。
BLOB:二进制LOB。存储大量的二进制信息,如字处理文档,图像换。应用向BLOB中写入什么位和字节,BLOB就会返回什么为和字节。
BFILE:二进制文件LOB。带BFILE列的数据库中存储的只是操作系统中某个文件的一个指针。这个文件在数据库之外维护,根本不是数据库的一部分。BFILE提供了文件内容的只读访问。
1 内部LOB
scott@ORCL>create table t
2 ( id int primary key,
3 txt clob
4 )
5 /
表已创建。
scott@ORCL>select dbms_metadata.get_ddl( 'TABLE', 'T' )
2 from dual;
DBMS_METADATA.GET_DDL('TABLE','T')
--------------------------------------------------------------------------------
CREATE TABLE "SCOTT"."T"
( "ID" NUMBER(*,0),
"TXT" CLOB,
PRIMARY KEY ("ID")
USING INDEX PCTFREE 10 INITRANS 2 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "TOOLS" ENABLE
) SEGMENT CREATION DEFERRED
PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
TABLESPACE "TOOLS"
LOB ("TXT") STORE AS BASICFILE (
TABLESPACE "TOOLS" ENABLE STORAGE IN ROW CHUNK 8192 RETENTION
NOCACHE LOGGING )
LOB显然有以下属性:
一个表空间(这个例子中即为TOOLS)
ENABLE STORAGE IN ROW作为一个默认属性
CHUNK 8192
RETENTION
NOCACHE
LOB列总是会带来一种多段对象(multisegment object),这个表会使用多个物理段。
实际LOB数据存储在lobsegment中,lobindex用于执行LOB的导航,来找出其中的某些部分。创建一个LOB列时,一般来说,存储在行中的这是一个指针(pointer),或LOB定位器(LOB locator)。应用所获取的就是这个LOB定位器。当请求得到LOB的“1,2000~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列为我们透明地实现了这种主/明细结构。
1. LOB表空间
从DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS BASICFILE (TABLESPACE "TOOLS"....
TABLESPACE 存储lobsegment和lobindex表空间,这可能与表本身所在的表空间不同。也就是说,保存LOB数据的表空间可能不同于保存实际表数据的表空间。从管理的角度看,LOB数据类型表示一种规模很大的信息。如果表有数百万行,而每行有一个很大的LOB,那么LOB就会极为庞大。为LOB数据单独使用一个表空间有利于备份和恢复以及空间管理,将表与LOB数据分离就很有意义。例如,LOB数据使用另外一个统一的区段大小,而不是普通表数据所用的区段大小。另一个原因则出于I/O性能的考虑。默认情况下,LOB不在缓冲区缓存中进行缓存。因此,默认情况下,对于每个LOB访问,不论是读还是写,都会带来一个物理I/O(从磁盘直接读,或者向磁盘直接写)。
LOB可能是内联的(inline),或者存储在表中。在这种情况下,LOB数据会被缓存,但是这只适用于小于4,000字节的LOB。
lobindex和lobsegment总是会在同一个表空间中。实际上,lobindex的所有存储特征都是从lobsegment继承的。
2. IN ROW子句
前面的DBMS_METADATA返回的CREATE TABLE语句还包括以下内容:
LOB ("TXT") STORE AS BASICFILE (...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总是能在表本身中放下,就应该采用这种默认行为,这样既能避免单独存储的开销,又能避免获取LOB时所需的物理I/O。
我们将创建包括有两个LOB的表,其中一个LOB可以在行内存储数据,而另一个LOB禁用了行内存储:
scott@ORCL>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 /
表已创建。
在这个表中,我们将插入一些串数据,所有这些串的长度都不超过4,000字节:
scott@ORCL>insert into t
2 select rownum,
3 owner || ' ' || object_name || ' ' || object_type || ' ' || status,
4 owner || ' ' || object_name || ' ' || object_type || ' ' || status
5 from all_objects
6 /
已创建72081行。
scott@ORCL>commit;
提交完成。
现在,想读取每一行,在此使用了DBMS_MONITOR包,并启用了SQL_TRACE(具体参见 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 72081 1.09 1.28 0 0 0 0
Fetch 72081 1.18 1.11 0 216243 0 72081
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 144163 2.27 2.40 0 216243 0 72081
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID T (cr=3 pr=0 pw=0 time=0 us cost=1 size=2015 card=1)
1 INDEX UNIQUE SCAN SYS_C0020634 (cr=2 pr=0 pw=0 time=0 us cost=1 size=0 card=1)(object id 85254)
********************************************************************************
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 72081 1.13 0.88 0 0 0 0
Fetch 72081 7.47 26.76 72081 504567 0 72081
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 144163 8.61 27.65 72081 504567 0 72081
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
1 TABLE ACCESS BY INDEX ROWID T (cr=3 pr=0 pw=0 time=0 us cost=1 size=253 card=1)
1 INDEX UNIQUE SCAN SYS_C0020634 (cr=2 pr=0 pw=0 time=0 us cost=1 size=0 card=1)(object id 85254)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Disk file operations I/O 1 0.00 0.00
direct path read 72008 0.18 18.79
获取IN_ROW列显著地快得多,而且所占用的资源也远远少于OUT_ROW列。可以看到,它使用了216,243次逻辑I/O(查询模式获取),而OUT_ROW列使用的逻辑I/O次数是它的两倍。这些额外的逻辑I/O 是对lobindex段的I/O(为了找到LOB的各个部分)。
另外,可以看到,对于OUT_ROW列,获取72081行会带来72081次物理I/O,而这会导致同样数目的“直接路径读”I/O等待。这些都是对非缓存LOB数据的读取。在这种情况下,通过启用LOB数据的缓存,可以缓解这个问题,但是这样一来,我们又必须确保为此要有足够多的额外的缓冲区缓存。另外,如果确实有非常大的LOB,我们可能并不希望缓存这些数据。
这种行内/行外存储设置不仅会影响读,还会影响修改。如果我们要用小串更新前100行,并用小串插入100个新行,再使用同样的技术查看性能,会观察到:
scott@ORCL>alter system set timed_statistics=true scope=both;
系统已更改。
scott@ORCL>alter session set sql_trace = true;
会话已更改。
scott@ORCL>create sequence s start with 100000;
序列已创建。
scott@ORCL>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 =
9 to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') where id = i;
10 update t set out_row =
11 to_char(sysdate,'dd-mon-yyyy hh24:mi:ss') where id = i;
12 insert into t (id, in_row) values ( s.nextval, 'Hello World' );
13 insert into t (id,out_row) values ( s.nextval, 'Hello World' );
14 end loop;
15 end;
16 /
PL/SQL 过程已成功完成。
scott@ORCL>show parameter user_dump_dest;
NAME TYPE VALUE
------------------------ ------ ------------------------------------
user_dump_dest\diag\rdbm string d:\app\administrators\orcl\orcl\trace
scott@ORCL> select username,sid,serial# from v$session where username='SCOTT';
USERNAME SID SERIAL#
-------- ----- --------
SCOTT 11 108
scott@ORCL>select 'orcl_ora_'||spid||'.trc' from v$process where addr = (select
paddr from v$session where sid=11);
'ORCL_ORA_'||SPID||'.TRC'
--------------------------------------------------------------------------
orcl_ora_4000.trc
scott@ORCL>alter session set sql_trace = false;
会话已更改。
scott@ORCL>exit
从 Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options 断
开
C:\Users\Administrator>d:
D:\>cd app\Administrator\diag\rdbms\orcl\orcl\trace
D:\app\Administrator\diag\rdbms\orcl\orcl\trace>tkprof orcl_ora_4000.trc f:\2018
0906.txt
TKPROF: Release 11.2.0.1.0 - Development on 星期四 9月 6 14:32:55 2018
Copyright (c) 1982, 2009, Oracle and/or its affiliates. All rights reserved.
D:\app\Administrator\diag\rdbms\orcl\orcl\trace>
在得到的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.01 0.04 0 201 208 100
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 0.01 0.04 0 201 208 100
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
0 UPDATE T (cr=3 pr=0 pw=0 time=0 us)
1 INDEX UNIQUE SCAN SYS_C0020634 (cr=2 pr=0 pw=0 time=0 us cost=1 size=2015 card=1)(object id 85254)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
direct path write 84 0.00 0.02
asynch descriptor resize 163 0.00 0.00
********************************************************************************
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.01 0.11 3 1657 2606 100
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 0.01 0.11 3 1657 2606 100
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
0 UPDATE T (cr=12 pr=1 pw=1 time=0 us)
1 INDEX UNIQUE SCAN SYS_C0020634 (cr=2 pr=0 pw=0 time=0 us cost=1 size=253 card=1)(object id 85254)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
Disk file operations I/O 2 0.00 0.00
db file sequential read 1 0.02 0.02
direct path write 10 0.00 0.00
asynch descriptor resize 18 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.00 0.01 1 3 324 100
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 0.00 0.01 1 3 324 100
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
0 LOAD TABLE CONVENTIONAL (cr=2 pr=1 pw=0 time=0 us)
1 SEQUENCE S (cr=1 pr=0 pw=0 time=0 us)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
db file sequential read 3 0.01 0.01
direct path write 92 0.00 0.02
asynch descriptor resize 178 0.00 0.00
********************************************************************************
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.03 0.10 1 1188 1947 100
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 101 0.03 0.10 1 1188 1947 100
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 84 (recursive depth: 1)
Rows Row Source Operation
------- ---------------------------------------------------
0 LOAD TABLE CONVENTIONAL (cr=11 pr=0 pw=1 time=0 us)
1 SEQUENCE S (cr=0 pr=0 pw=0 time=0 us)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
direct path write 1 0.00 0.00
注意读和写使用的I/O都有所增加。总之,由此显示出,如果使用一个CLOB,而且很多串都能在“行内”放下(也就是说,小于4,000字节),那么使用默认的ENABLE STORAGE IN ROW设置就是一个不错的想法。
3. CHUNK子句
前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS BASICFILE (...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。另外,这还会影响获取性能,因为与读取更少但更大的CHUNK相比,现在要花更长的数据来读取和管理许多小CHUNK。我们最终的目标是:使用一个能使“浪费”最少,同时又能高效存储数据的CHUNK大小。
4. PCTVERSION子句
控制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中):
scott@ORCL>create table t
2 ( id int primary key,
3 txt clob
4 )
5 lob( txt) store as ( disable storage in row )
6 /
表已创建。
scott@ORCL>insert into t values ( 1, 'hello world' );
已创建 1 行。
scott@ORCL>commit;
提交完成。
如果取出LOB定位器,并在这个表上打开一个游标,如下:
scott@ORCL>declare
2 l_clob clob;
3
4 cursor c is select id from t;
5 l_id number;
6
7 begin
8 select txt into l_clob from t; ## 取出LOB定位器
9 open c; ## 并在这个表上打开一个游标
10
11 update t set id = 2, txt = 'Goodbye'; ## 然后修改行,并提交
12 commit;
13 #通过使用LOB定位器和打开的游标,会提供“获取LOB定位器或打开游标那个时间点”的数据
14 dbms_output.put_line( dbms_lob.substr( l_clob, 100, 1 ) );
15 fetch c into l_id;
16 dbms_output.put_line( 'id = ' || l_id );
17 close c;
18 end;
19 /
hello world
id = 1
PL/SQL 过程已成功完成。
但是数据库中的数据很可能已经更新/修改:
scott@ORCL>select * from t;
ID TXT
---------- --------
2 Goodbye
游标C的读一致映像来自undo段,而LOB的读一致映像则来自LOB段本身。
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子句
前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS BASICFILE (... RETENTION ...
这个子句与PCTVERSION子句是互斥的,如果数据库中使用自动undo管理,就可以使用这个子句。RETENTION子句在lobsegment中使用基于时间的机制来保留数据。数据库会设置参数UNDO_RETENTION,指定要把undo信息保留多长时间来保证一致读。在这种情况下,这个参数也适用于LOB数据。
不能使用这个子句来指定保留时间;而要从数据库的UNDO_RETENTION设置来继承它。
6. CACHE子句
前面的DBMS_METADATA返回的CREATE TABLE语句包括以下内容:
LOB ("TXT") STORE AS BASICFILE (... 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子句
它有一个完整的存储子句,可以用来控制物理存储特征。这个存储子句同样适用于lobsegment和lobindex,对一个段的设置也可以用于另一个段。假设有一个本地管理的表空间,LOB的相关设置将是FREELISTS、FREELIST GROUPS和BUFFER_POOL。FREELISTS和FREELIST GROUPS与表段的关系 同样适用于lobindex段,因为lobindex与其他索引段的管理是一样的。如果需要高度并发地修改LOB,可能最好在索引段上设置多个FREELISTS。
对LOB段使用保持池或回收池可能是一个很有用的技术,这样就能缓存LOB数据,而且不会“破坏”现有的默认缓冲区缓存。并不是将LOB与常规表一同放在块缓冲区中,可以在SGA中专门为这些LOB对象预留一段专用的内存。BUFFER_POOL子句可以达到这个目的。
2. BFILE
BFILE类型只是操作系统上一个文件的指针。它用于为这些操作系统文件提供只读访问。
使用BFILE时,还有使用一个Oracle DIRECTORY对象。DIRECTORY对象只是将一个操作系统目录映射至数据库中的一个“串”或一个名称(以提供可移植性;你可能想使用BFILE中的一个串,而不是操作系统特定的文件名约定)。作为一个小例子,下面创建一个带BFILE列的表,并创建一个DIRECTORY对象,再插入一行,其中引用了文件系统中的一个文件:
scott@ORCL>create table t
2 ( id int primary key,
3 os_file bfile
4 )
5 /
表已创建。
scott@ORCL>create or replace directory my_dir as '/tmp/'
2 /
目录已创建。
scott@ORCL>insert into t values ( 1, bfilename( 'MY_DIR', 'test.dbf' ) );
已创建 1 行。
现在,就可以把BFILE当成一个LOB来处理,因为它就是一个LOB。例如,可以做下面的工作:
scott@ORCL>create or replace directory MY_DIR as 'd:/test';
目录已创建。
scott@ORCL>insert into t values ( 1, bfilename( 'MY_DIR', '11.txt'));
已创建 1 行。
scott@ORCL>select dbms_lob.getlength(os_file) from t;
DBMS_LOB.GETLENGTH(OS_FILE)
---------------------------
28
可以看到所指定的文件大小为28kb。如果使用混合大小写或小写,会得到以下错误:
scott@ORCL>update t set os_file = bfilename( 'my_dir', '11.txt' );
已更新 1 行。
scott@ORCL>select dbms_lob.getlength(os_file) from t;
select dbms_lob.getlength(os_file) from t
*
第 1 行出现错误:
ORA-22285: 对不存在的目录或文件进行 GETLENGTH 操作
ORA-06512: 在 "SYS.DBMS_LOB", line 787
Oracle中的DIRECTORY对象是标识符,而默认情况下标识符都以大写形式存储。BFILENAME内置函数接受一个串,这个串的大小写必须与数据字典中存储的DIRECTORY对象的大小写完全匹配。所以,必须在BFILENAME函数中使用大写,或者在创建DIRECTORY对象时使用加引号的标识符:
scott@ORCL>create or replace directory "my_dir" as 'd:/test';
目录已创建。
scott@ORCL>select dbms_lob.getlength(os_file) from t;
DBMS_LOB.GETLENGTH(OS_FILE)
---------------------------
28
不建议使用加引号的标识符;而倾向于在BFILENAME调用中使用大写。加引号的标识符属于“异类”,可能会在以后导致混淆。
BFILE在磁盘上占用的空间不定,这取决于DIRECTORY对象名的文件名的长度。
与其他LOB数据不同,BFILE数据不是“读一致”的。由于BFILE在数据库之外管理,对BFILE解除引用时,不论文件上发生了什么,都会反映到得到的结果中。所以,如果反复读同一个BFILE,可能会产生不同的结果,这与对CLOB、BLOB或NCLOB使用LOB定位器不同。
ROWID/UROWID类型
ROWID是数据库中一行的地址。ROWID中编入了足够多的信息,足以在磁盘上找到行,以及标识ROWID所指向的对象(表等)。ROWID有一个“近亲”UROWID,它用于表,如IOT和通过异构数据库网关访问的没有固定ROWID表。UROWID是行主键值的一个表示,因此,其大小不定,这取决于它指向的对象。
每个表中的每一行都有一个与之关联的ROWID或UROWID。从表中获取时,把它们看作为伪列(pseudo column),这说明它们并不真正存储在行中,而是行的一个推导属性。ROWID基于行的物理位置生成;它并不随行存储。UROWID基于行的主键生成,所以从某种意义上讲,好像它是随行存储的,但是事实上并非如此,因为UROWID并不作为一个单独的列存在,而只是作为现有列的一个函数。
以前ROWID是不可变的。插入一行时,会为之关联一个ROWID(一个地址),而且这个ROWID会一直与该行关联,直到这一行被删除(被物理地从数据库删除)。但是,后来情况发生了变化,因为现在有些操作可能会导致行的ROWID改变,例如:
在分区表中更新一行的分区键,使这一行必须从一个分区移至另一个分区。
使用FLASHBACK TABLE命令将一个数据库表恢复到以前的每个时间点。
执行MOVE操作以及许多分区操作,如分解或合并分区。
使用ALTER TABLE SHRINK SPACE命令执行段收缩。
如今,由于ROWID可能过一段时间会改变(因为它不再是不可变的),所以不建议把它们作为单独的列物理地存储在数据库表中。也就是说,使用ROWID作为一个数据库列的数据类型被认为是一种不好的实践做法。应当避免这种做法,而应使用行的主键(这应该是不可变的),另外引用完整性可以确保数据的完整性。对此用ROWID类型是做不到的,不能用ROWID创建从子表到一个父表的外键,而且不能保证跨表的完整性。你必须使用主键约束。
ROWID作为行的一个物理地址,要访问任何表中的某一行,这是最快的方法。如果应用从数据库读出数据并将其提供给最终用户,它试图更新这一行时就可以使用ROWID。应用这种方式,只需最少的工作就可以更新当前行(例如,不需要索引查找再次寻找行),并通过验证行值未被修改来确保这一行与最初读出的行是同一行。所以,在采用乐观锁定的应用中ROWID还是有用的。