SecureFiles:新 LOB
了解如何使用新一代 LOB:SecureFiles。SecureFiles 集外部文件和 LOB 方法的优点于一身,可以存储非结构化数据,允许加密、压缩、重复消除等等。
数据库驻留 BLOBS 或 OS 文件
您在 数据库中存储什么?通常,您会以关系格式存储数据以便于映射到某些定义模式的类型,或者以客户姓名、帐户余额、状态代码等定义的数据类型存储数据。但是,以非结构化或半结构化格式存储信息的需求也日益增加。例如,照片、字处理文档、电子表格、XML 文件等等。这些类型的数据如何存储?
通常有两种方法:这些数据作为 LOB 字段(BLOB 用于存储二进制数据,CLOB 用于存储字符数据)存储在数据库中,或者通过引用存储在数据库中的文件存储在 OS 文件中。
每种方法都各具优缺点。OS 文件可以由 OS 和日志文件系统缓存以加速崩溃后的。由于可进行压缩,因此 OS 文件占用的空间通常也比数据库中的数据要少。
还有一些工具可以智能地识别文件模式并消除重复从而提高存储效率;但是 OS 文件位于数据库外部,因此数据库属性不适用于它们。这些文件不进行、细粒度安全性不适用于它们,此类文件不是事务的一部分 — 因此 Oracle 数据库固有的读取一致性等该概念不适用于它们。
如果能集两种方法的优点于一身会怎样?Oracle 数据库 11g中的 SecureFiles 为您提供了答案。SecureFiles 是数据库中一个全新的基础架构,可提供最佳的数据库驻留 LOB 和 OS 文件性能。让我们看一看这种的实现方式。(顺便说明的是,传统 LOB 仍然以 BasicFiles 格式提供。)
实际示例
通过一个简单的示例来介绍 SecureFiles 概念或许是一种最佳方式。假设您要开发一个合同系统,在该系统中您希望将所有合同的副本都放在一个表中。扫描的文档通常是 PDF 文件而非文本。某些可能是 MS Word 文档或者甚至是扫描的照片。这是 BLOB 的最佳使用案例,因为列必需能够支持二进制数据。
通常,在 Oracle 数据库 11g推出之前,您可能会按照以下方式定义表:
create contracts_basic
(
contract_id number(12),
contract_name varchar2(80),
file_size number,
orig_file blob
)
tablespace users
lob (orig_file)
(
tablespace users
enable storage in row
chunk 4096
pctversion 20
nocache
nologging
);
\
实际的文件以二进制格式存储在 ORIG_FILE 列中。各种参数指明在操作期间 LOB 不应缓存并记入日志,应按表行存储,块大小应为 4KB 并存储在表空间 USERS 中。由于您没有明确指定,因此 LOB 在 Oracle 数据库 11g中以常规格式 (BasicFiles) 存储。
如果您将 LOB 存储为 SecureFile,只需在创建表时放入一个子句store as securefile,如下所示:
create table contracts_sec
(
contract_id number(12),
contract_name varchar2(80),
file_size number,
orig_file blob
)
tablespace users
lob (orig_file)store as securefile(
tablespace users
enable storage in row
chunk 4096
pctversion 20
nocache
nologging
)
/
要创建 SecureFile LOB,您需要满足两个条件,而这两个条件都是默认设置,因此您可能已经满足。
- 初始化参数 db_securefile 应设为 permitted(默认设置)。我会在后面部分说明该参数的作用。
- 在其中创建 securefile 的表空间应启用自动段空间管理 (ASSM)。在 Oracle 数据库 11g中,表空间创建的默认模式为 ASSM,因此该表空间可能已经这样设置。如果没有,则需要在一个新的 ASSM 表空间上创建 SecureFile。
表创建完成后,您可以加载数据,方式与 11g之前的常规 LOB (BasicFile) 相同。不需要更改应用程序,也不需要记住某些特殊的语法。
下面是一个载入该表的小程序。
declare
l_size number;
l_file_ptr bfile;
l_blob blob;
begin
l_file_ptr := bfilename('SECFILE', 'contract.pdf');
dbms_lob.fileopen(l_file_ptr);
l_size := dbms_lob.getlength(l_file_ptr);
for ctr in 1 .. 100 loop
insert into contracts_sec
(
contract_id,
contract_name,
file_size,
orig_file
)
values
(
ctr,
'Contract '||ctr,
null,
empty_blob()
)
returning orig_file into l_blob;
dbms_lob.loadfromfile(l_blob, l_file_ptr, l_size);
end loop;
commit;
dbms_lob.close(l_file_ptr);
end;
/
该程序将 contract.pdf 文件分 100 次载入表的 100 个行中。您应该已经为存储 contract.pdf 文件的 OS 定义了一个名为 SECFILE 的目录对象。下面的示例指明 contract.pdf 文件在 /opt/oracle 中的位置。
SQL> create directory secfile for ’/opt/oracle’;
将 LOB 存储为 SecureFile 后,您就可以获得很多能优化操作的特性。下面就是其中一些很有用的特性。
重复消除
重复消除可能是 SecureFiles 中最受欢迎的特性,它由于 OS 文件在某些高端文件系统中相对于数据库驻留 BLOB 的优势而倍受推崇。假设一个表有五个记录,每个记录各有一个 BLOB。其中三个 BLOB 是完全相同的。如果能够只存储该 BLOB 一次而在其他两个记录上只存储对该副本的引用将极大地减少空间消耗。这在 OS 在文件中是可行的,但在 Oracle 数据库 10gLOB 中则无法实现。但是如果使用 SecureFiles,只需通过一个称为重复消除的属性即可轻松搞定。可以在表创建时指定该属性,也可以在以后将其修改为:
SQL> alter table contracts_sec
2 modify lob(orig_file)
3 (deduplicate)
4 /
Table altered.
使用重复消除后,数据库计算每行中各列的散列值并进行相互比较。如果有匹配的散列值,则存储该散列值而不是实际的 BLOB。新记录插入时会计算其散列值,如果其散列值与其他散列值匹配,则插入散列值;否则存储实际的值。
下面,我们来看看进行重复消除过程后的空间节省情况。可以通过程序包 DBMS_SPACE 检查 LOB 段中的空间消耗。下面是一个显示空间消耗的程序:
declare
l_segment_name varchar2(30);
l_segment_size_blocks number;
l_segment_size_bytes number;
l_used_blocks number;
l_used_bytes number;
l_expired_blocks number;
l_expired_bytes number;
l_unexpired_blocks number;
l_unexpired_bytes number;
begin
select segment_name
into l_segment_name
from dba_lobs
where table_name = 'CONTRACTS_SEC';
dbms_output.put_line('Segment Name=' || l_segment_name);
dbms_space.space_usage(
segment_owner => 'ARUP',
segment_name => l_segment_name,
segment_type => 'LOB',
partition_name => NULL,
segment_size_blocks => l_segment_size_blocks,
segment_size_bytes => l_segment_size_bytes,
used_blocks => l_used_blocks,
used_bytes => l_used_bytes,
expired_blocks => l_expired_blocks,
expired_bytes => l_expired_bytes,
unexpired_blocks => l_unexpired_blocks,
unexpired_bytes => l_unexpired_bytes
);
dbms_output.put_line('segment_size_blocks => '|| l_segment_size_blocks);
dbms_output.put_line('segment_size_bytes => '|| l_segment_size_bytes);
dbms_output.put_line('used_blocks => '|| l_used_blocks);
dbms_output.put_line('used_bytes => '|| l_used_bytes);
dbms_output.put_line('expired_blocks => '|| l_expired_blocks);
dbms_output.put_line('expired_bytes => '|| l_expired_bytes);
dbms_output.put_line('unexpired_blocks => '|| l_unexpired_blocks);
dbms_output.put_line('unexpired_bytes => '|| l_unexpired_bytes);
end;
/
该脚本显示 LOB 的各种与空间有关的统计信息。进行重复消除过程之前,输出如下:
Segment Name=SYS_LOB0000070763C00004$$
segment_size_blocks => 1072
segment_size_bytes => 8781824
used_blocks => 601
used_bytes => 4923392
expired_blocks => 448
expired_bytes => 3670016
unexpired_blocks => 0
unexpired_bytes => 0
使用重复消除后:
Segment Name=SYS_LOB0000070763C00004$$
segment_size_blocks => 1456
segment_size_bytes => 11927552
used_blocks => 7
used_bytes => 57344
expired_blocks => 127
expired_bytes => 1040384
unexpired_blocks => 1296
unexpired_bytes => 10616832
仅以上输出中的一个度量标准就足以说明问题:used_bytes。它显示了 LOB 列存储的字节数。使用重复消除之前,需要占用 4,923,392 字节或 5MB,但使用重复消除后,该值缩减为 57,344 字节或大约 57KB,差不多是原始值的 1%。这是因为重复消除过程发现了 100 个具相同值的行(回忆一下,我们将同一个值放到了所有行的 LOB 列中)。重复消除过程只保留了一行而使其他行成为指针。
您也可以反向进行该重复消除过程:
SQL> alter table contracts_sec
2 modify lob(orig_file)
3 (keep_duplicates)
4 /
Table altered.
之后,如果您再检查空间:
Segment Name=SYS_LOB0000070763C00004$$
segment_size_blocks => 1456
segment_size_bytes => 11927552
used_blocks => 601
used_bytes => 4923392
expired_blocks => 0
expired_bytes => 0
unexpired_blocks => 829
unexpired_bytes => 6791168
您将发现 USED_BYTES 增加,变为大约 5MB 的原始值。
压缩
SecureFiles 的另一个特性是压缩。可以使用以下 压缩存储在 LOB 中的值:
SQL> alter table contracts_sec
2 modify lob(orig_file)
3 (compress high)
4 /
Table altered.
现在,如果您运行空间查找 PL/SQL 块:
Segment Name=SYS_LOB0000070763C00004$$
segment_size_blocks => 1456
segment_size_bytes => 11927552
used_blocks => 201
used_bytes => 1646592
expired_blocks => 0
expired_bytes => 0
unexpired_blocks => 1229
unexpired_bytes => 10067968
您将发现 used_bytes 这一度量标准由 5MB 降至现在的 1,646,592 或大约 1.5 MB。
压缩与重复消除不同。压缩在每行的 LOB 列内部发生 — 每个 LOB 列单独压缩。在重复消除过程中,所有行都受到检查,列中的重复值将进行删除并替换为指针。如果您有两个完全不同的行,重复消除不会减少占用空间;但是压缩可以优化 LOB 值的空间。除了对表执行重复消除过程,您还可以对表进行压缩。
由于压缩占用 CPU 循环,因此如果数据压缩量不大,可能并不值得进行压缩。例如,如果您有大量已经压缩过的 JPEG 照片,则进一步的压缩不会节省任何空间。但是,如果您有一个作为 CLOB 存储的 XML 文档,则压缩可以节省大量空间。SecureFiles 压缩自动检测数据是否可压缩并仅在压缩可以节省空间时占用 CPU 循环。
Oracle Text 索引可以在压缩过的 SecureFiles LOB 上安装。较之文件系统中的压缩文件,这是在 Oracle 数据库中存储非结构化数据的主要好处。
此外,请注意,LOB 压缩与表压缩无关。压缩表 CONTRACTS_SEC 并不会压缩 LOB。LOB 压缩仅在您执行上述 SQL 时才会发生。
加密
您可以对 SecureFiles 使用透明数据库加密,就像您将对任何列所做的一样。下面说明如何使用 AES 128 位加密对列 orig_file LOB 进行加密。
alter table contracts_sec
modify lob(orig_file)
(encrypt using 'AES128')
/
启用加密之前,您需要设置加密钱夹。(加密钱夹的完整描述可以在此Oracle Magazine 文章中找到。)下面是总结的几个步骤:
- 在 sqlnet.ora 中设置参数(如果尚未设置)以指定钱夹的位置:
ENCRYPTION_WALLET_LOCATION=
(SOURCE=
(METHOD=FILE)
(METHOD_DATA=
(DIRECTORY= /opt/oracle/orawall)
)
)
目录 /opt/oracle/orawall 应该已经存在;如果不存在,则应该创建该目录。
- 创建钱夹:
alter system set encryption key authenticated by "mypass"
这将创建口令为 mypass 的钱夹并将其打开。
- 上述步骤只需执行一次。钱夹创建并打开后,只要数据库在运行它就会一直打开(除非人为关闭)。如果数据库重启,您需要通过以下语句打开钱夹:
alter system set encryption wallet open identified by "mypass"
当 SecureFile LOB 列得到加密后,该表所有行的列值都会得到加密。加密后,您不能在表中使用常规导出或导入;您需要使用数据泵。
您可以查看视图 dba_encrypted_columns 了解哪些列已得到加密以及加密的方式。
SQL> select table_name, column_name, encryption_alg
2 from dba_encrypted_columns
3 /
TABLE_NAME COLUMN_NAME ENCRYPTION_ALG
------------------------------ ------------------ -----------------------------
CONTRACTS_SEC ORIG_FILE AES 128 bits key
缓存
较之数据库驻留对象,在 OS 文件中存储非结构化数据的优势之一是缓存工具。文件可以在操作系统的文件缓冲区中进行缓存。数据库驻留对象还可以在数据库缓冲区缓存中进行缓存。但是,在某些情况下,缓存可能竟会损害性能。LOB 通常都很大(该术语大对象就是因此得名),如果它们进入缓冲区缓存,大多数其他的数据块将需要被推送出缓存以为要进来的 LOB 腾出空间。该 LOB 可能以后永远都不会使用,但是它进入缓冲区缓冲却会导致某些必需的数据块流出。因此,在大多数情况下,您可能希望对 LOB 禁用缓存。
在针对 CONTRACTS_SEC 的示例脚本中,您使用了 nocache 子句来禁用缓存。要为 LOB 启用缓存,您可以对该表进行以下更改:
alter table contracts_sec
modify lob(orig_file)
(cache)
/
这将启用 LOB 缓存。注意该缓存只引用 LOB。表的其余部分放入缓冲区缓存,并遵循任何其他表的逻辑(无论该表上的 LOB 缓存如何设置)。
缓存的优点是非常依赖于应用程序。在处理缩略图的应用程序中,使用缓存可能会提高性能。但是,对于大型文档或图像,最好关闭缓存。您可以通过 securefiles 进行控制。
日志记录
日志记录子句决定 LOB 中的数据更改如何记录到重做日志流中。与任何其他数据一样,默认设置为完全日志记录,但是由于 LOB 中的数据通常都很大,在某些情况下,您可能希望不进行日志记录。上述示例中的 NOLOGING 子句就可以实现该目的。
SecureFiles 为该子句提供了另一个值filesystem_like_logging,如下所示:
create table contracts_sec_fs
(
contract_id number(12),
contract_name varchar2(80),
file_size number,
orig_file blob
)
tablespace users
lob (orig_file)
store as securefile
(
tablespace users
enable storage in row
chunk 4096
pctversion 20
nocachefilesystem_like_logging)
注意以黑体显示的行,它将 LOB 元数据记录到重做日志中,而不是记录整个 LOB。这类似于文件系统。文件元数据记录到文件系统日志中。同样,SecureFiles 上的该子句会加速崩溃后的恢复。
管理
数据字典视图 DBA_LOBS 显示了数据库中 LOB 的属性(包括 SecureFiles)。下面是该视图的列:
列名
|
说明
|
OWNER |
表的所有者 |
TABLE_NAME |
表的名称 |
COLUMN_NAME |
LOB 列的名称 |
SEGMENT_NAME |
LOB 作为单独的段存储,由用户命名,默认为 SYS_LOB… |
TABLESPACE_NAME |
表空间的名称 |
INDEX_NAME |
LOB 索引的名称 |
CHUNK |
LOB 的块大小 |
PCTVERSION |
在 SecureFiles 中忽略 |
RETENTION |
如果 SecureFile LOB 进行了更新,以前的图像与任何其他数据库块一样保存在还原段中;但是与数据库块不同的是,您可以指定以前的图像保存多长时间(保留期)。 |
FREEPOOLS |
对 SecureFiles 忽略 |
CACHE |
SecureFile LOB 是否在缓冲池中缓冲(是/否),本文已说明 |
LOGGING |
是否记录对 SecureFile LOB 进行的更改(是/否),本文已说明 |
ENCRYPT |
SecureFile LOB 是否已加密(是/否),本文已说明 |
COMPRESSION |
SecureFile LOB 是否已压缩(是/否),本文已说明 |
DEDUPLICATION |
Securefile LOB 是否已进行重复消除(是/否),本文已说明 |
IN_ROW |
LOB 是否按表行存储 |
FORMAT |
LOB 是否与平台的字节顺序有关 |
PARTITIONED |
LOB 是否在分区表上 |
SECUREFILE |
LOB 是 SECUREFILE(是或否)还是 BASICFILE |
在分区表上,LOB 信息存储在视图 DBA_LOB_PARTITIONS 中。
LOB 到 SecureFiles 的移植
既然已经了解 SecureFiles 是多么有用,您可能希望对现有的表进行转换。最简单的方法是创建一个新表,载入旧表中的数据,然后重命名该表。(当然,这要求这些表在操作期间不可用。)另一种方法是使用 dbms_redefinition 程序包在线重新定义表,不影响可用性。
我们通过一个示例来了解该过程。假设您希望移植原始表 CONTRACTS_BASIC 以存储为 SecureFiles。要实现该目的,执行以下步骤。
- 确保您具有一个主键。如果没有,创建一个。
alter table contracts_basic
add constraint pk_contacts
primary key (contract_id)
/
- 构建新表。
create table contracts_new
(
contract_id number(12),
contract_name varchar2(80),
file_size number,
orig_file BLOB
)
lob (orig_file)
store as securefile
(nocache nologging)
/
- 将列映射到新表。
declare
l_col_mapping varchar2(1000);
begin
l_col_mapping :=
'contract_id contract_id , '||
'contract_name contract_name , '||
'file_size file_size, '||
'orig_file orig_file';
dbms_redefinition.start_redef_table
('ARUP', 'CONTRACTS_BASIC', 'CONTRACTS_NEW', l_col_mapping);
end;
/
- 开始重新定义过程。
declare
l_error_count pls_integer := 0;
begin
dbms_redefinition.copy_table_dependents
(
'ARUP', 'CONTRACTS_BASIC', 'CONTRACTS_NEW',
1, TRUE, TRUE, TRUE, FALSE, l_error_count
);
dbms_output.put_line('Errors Occurred := ' ||
to_char(l_error_count));
end;
/
这会将 CONTRACTS_BASIC 中的所有行复制到 CONTRACTS_NEW 中,因此,根据表的行数,该操作可能需要较长时间。
- 完成重新定义过程。
begin
dbms_redefinition.finish_redef_table
('ARUP', 'CONTRACTS_BASIC', 'CONTRACTS_NEW');
end;
/
- 确认表已得到转换。
select securefile
from dba_lobs
where table_name = 'CONTRACTS_BASIC'
/
SEC
---
YES
列显示 YES,表明列已转换为 SecureFiles。
- 删除临时表 CONTRACTS_NEW。
SQL> drop table contracts_new;
Table dropped.
您可以尝试在开始就启用并行 DML 以加快复制过程。下面说明如何在会话中启用并行 DML:
alter session force parallel dml;
初始化参数
初始化参数 db_securefile 决定 SecureFiles 在数据库中的使用。下面是该参数的各种值及其效果:
值 |
效果 |
PERMITTED |
默认值。该值指明可以在数据库中创建 SecureFile LOB。 |
ALWAYS |
既然您已经知道了 SecureFiles 是多么有用,您可能希望确保所有 LOB 创建后就应该仅为 SecureFiles 而非默认的 BasicFiles(即使用户没有指定 securefile)。该参数值确保所有 LOB 默认情况下创建为 SecureFiles。记住,SecureFiles 需要 ASSM 表空间(在 11g中为默认设置),因此如果您尝试在非 ASSM 表空间中创建 LOB,将出现错误。 |
NEVER |
与 always 值相反。由于某种原因,您不喜欢 SecureFiles 并且不希望允许它在数据库中创建。即使使用 SecureFile 子句,该参数值仍然会将 LOB 创建为 BasicFile。当使用了 SecureFile 子句而 LOB 仍然创建为默认的 BasicFile 时,用户不会收到错误消息。 |
IGNORE |
忽略 securefile 子句以及所有存储子句。 |
结论
SecureFiles 不仅是新一代 LOB,它们还为 LOB 带来了更多的价值,尤其是以前只能在文件系统领域中获得的特性。SecureFiles 可以进行加密以确保安全性,可以进行重复消除和压缩以提高存储效率,可以进行缓存(或不进行缓存)以加快访问(或节省缓冲池空间),可以按多个级别记录以减少崩溃后的平均恢复时间。引入 SecureFiles 后,您可以在数据库中存储更多的非结构化文档,而不会导致过多的开销,也不会失去 OS 文件系统提供的任何重要功能。