在数据仓库中保存大列数据的最佳方法

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 值,然后将它们连接到另一个表。

【讨论】:

以上是关于在数据仓库中保存大列数据的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

在数据仓库中加载数据的最佳方式

数据仓库统计开发最佳实践

为数据仓库创建 SQL Server 数据库的最佳实践

牛人大讲堂:京东数据仓库运营最佳实践

什么是数据仓库,数据仓库在哪里保存数据。BI项目需要用到哪些技术

从我们仓库中其他 Snowflake DB 派生的数据中使自定义表保持最新的最佳实践