在数据仓库中保存大列数据的最佳方法
Posted
技术标签:
【中文标题】在数据仓库中保存大列数据的最佳方法【英文标题】:Best way to save large column data in datawarehouse 【发布时间】:2020-11-06 04:46:22 【问题描述】:我有一个存储事务更改的表。所有更改都被捕获到一个表中。作为事务的一部分出现的列之一可以有许多逗号分隔的值。发生的次数无法预测。此外,此字段不是强制性的,也可以有空值。
我在表中的事务总数约为 100M。其中,填充值的记录数为 1M。在 1M 的事务中,记录长度超过 4000 的记录数约为 37K。
我提到长度为 4000,因为在我的 oracle 表中,将保存它的列已定义为 varchar2(4000)。
我检查了一些地方,发现如果我必须保存一些未知长度的东西,那么我应该将表列数据类型定义为 clob。但是 clob 对我来说很昂贵,因为只有非常少量的数据长度 > 4000。如果我将星型模式雪花化并创建另一个表来存储值,那么即使我有长度远小于 4000 的事务保存为 clob 列的一部分。这在存储和性能方面都会很昂贵。
有人可以建议我解决这个问题的方法吗?
谢谢 S
【问题讨论】:
您好 - 此列的分析值是什么,它可能包含或不包含未知数量的逗号分隔元素 - 以及它是如何在您的报告/BI 中使用的?如果它没有分析价值,那你为什么要把它放在你的维度模型中? 【参考方案1】:您可以创建一个主从表来存储逗号分隔值,然后您可以有行而不是将所有逗号分隔值保存在单个列中。这可以通过使用主表和详细表之间的伪键的外键进行管理。
【讨论】:
【参考方案2】:这是一个选项。
创建两列,例如
create table storage
(id number primary key,
long_text_1 varchar2(4000),
long_text_2 varchar2(4000)
);
存储类似的值
insert into storage (id, long_text_1, long_text_2)
values (seq.nextval,
substr(input_value, 1, 4000),
substr(input_value, 4001, 4000)
);
从表中检索它们时,将它们连接起来:
select id,
long_text_1 || long_text_2 as long_text
from storage
where ...
【讨论】:
是的,想到了这个选项,但是..因为只有少数记录超过了最大长度,所以它不会成为大多数记录的冗余列。 那又怎样?它不会占用任何显着空间。 NULL 值将只占用一 (1) 个字节来指示该列的大小为零 (0)。【参考方案3】:您可能会从使用内联 SecurFile CLOB 中受益。使用内联 CLOB,最多可以将大约 4000 字节的数据存储在像常规 VARCHAR2 一样的行中,并且只有较大的值将存储在单独的 CLOB 段中。借助 SecureFiles,Oracle 可以显着提高 CLOB 性能。 (例如,SecureFiles 的导入和导出比老式的 BasicFile LOB 格式要快得多。)
根据您的版本、参数和表 DDL,您的数据库可能已经将 CLOB 存储为内联 SecureFiles。确保您的 COMPATIBLE 设置为 11.2 或更高版本,并且 DB_SECUREFILE 是“允许”、“始终”或“首选”之一:
select name, value from v$parameter where name in ('compatible', 'db_securefile') order by 1;
使用这样的查询来确保您的表设置正确,并且没有人覆盖系统设置:
select dbms_metadata.get_ddl('TABLE', 'YOUR_TABLE_NAME') from dual;
您应该在结果中看到如下内容:
... LOB ("CLOB_NAME") STORE AS SECUREFILE (... ENABLE STORAGE IN ROW ...) ...
CLOB 的一个主要问题是它们存储在单独的段中,并且必须遍历 LOB 索引才能将表中的每一行映射到另一个段中的值。下面的demo创建了两张表,说明当数据量小且内联存储时,不需要使用LOB段。
--drop table clob_test_inline;
--drop table clob_test_not_in;
create table clob_test_inline(a number, b clob) lob(b) store as securefile (enable storage in row);
create table clob_test_not_in(a number, b clob) lob(b) store as (disable storage in row);
insert into clob_test_inline select level, lpad('A', 900, 'A') from dual connect by level <= 10000;
insert into clob_test_not_in select level, lpad('A', 900, 'A') from dual connect by level <= 10000;
commit;
内联表段很大,因为它包含所有数据。异常表段很小,因为它的所有数据都保存在其他地方。
select segment_name, bytes/1024/1024 mb_inline
from dba_segments
where segment_name like 'CLOB_TEST%'
order by 1;
SEGMENT_NAME MB_INLINE
---------------- ---------
CLOB_TEST_INLINE 27
CLOB_TEST_NOT_IN 0.625
查看 LOB 段,大小是相反的。内联表不会在 LOB 段中存储任何内容。
select table_name, bytes/1024/1024 mb_out_of_line
from dba_segments
join dba_lobs
on dba_segments.owner = dba_lobs.owner
and dba_segments.segment_name = dba_lobs.segment_name
where dba_lobs.table_name like 'CLOB_TEST%'
order by 1;
TABLE_NAME MB_OUT_OF_LINE
------------ --------------
CLOB_TEST_INLINE 0.125
CLOB_TEST_NOT_IN 88.1875
尽管如此,我不能保证 CLOB 仍会为您工作。我只能说使用 CLOB 测试数据是值得的。你仍然需要注意一些事情。 CLOB 存储文本略有不同(UCS2 而不是 UTF8),这可能会占用更多空间,具体取决于您的字符集。所以检查段大小。但也要注意,分段大小可能会在它们较小时存在缺陷 - 样本数据有很多自动分配的开销,因此您需要在测试时使用实际大小。
最后,正如 Raul 所指出的,在字段中存储非原子值通常是一个可怕的错误。也就是说,数据仓库很少需要打破性能规则,并且需要尽可能紧凑地存储数据。在以这种方式存储数据之前,请确保您永远不需要基于这些值进行连接,或查询单个值。如果您认为处理 100M 行很困难,请等到您尝试拆分 100M 值,然后将它们连接到另一个表。
【讨论】:
以上是关于在数据仓库中保存大列数据的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章