【问题标题】:PostgreSQL: Loading data into Star Schema efficientlyPostgreSQL:有效地将数据加载到 Star Schema
【发布时间】:2011-06-02 19:41:51
【问题描述】:

想象一下 PostgreSQL 9.0 上具有以下结构的表:

create table raw_fact_table (text varchar(1000));

为了简单起见,我只提到一个文本列,实际上它有十几个。该表有 100 亿行,每列有很多重复项。该表是使用 COPY FROM 从平面文件 (csv) 创建的。

为了提高性能,我想转换为以下星型架构:

create table dimension_table (id int, text varchar(1000));

然后事实表将被替换为如下的事实表:

create table fact_table (dimension_table_id int);

我目前的方法基本上是运行以下查询来创建维度表:

Create table dimension_table (id int, text varchar(1000), primary key(id));

然后创建填充我使用的维度表:

insert into dimension_table (select null, text from raw_fact_table group by text);

之后我需要运行以下查询:

select id into fact_table from dimension inner join raw_fact_table on (dimension.text = raw_fact_table.text);

想象一下通过多次比较所有字符串和所有其他字符串得到的可怕性能。

在 MySQL 上,我可以在 COPY FROM 期间运行存储过程。这可以创建字符串的散列,并且所有后续字符串比较都在散列而不是长原始字符串上完成。这在 PostgreSQL 上似乎是不可能的,那我该怎么办?

示例数据将是一个包含类似内容的 CSV 文件(我也在整数和双精度数周围使用引号):

"lots and lots of text";"3";"1";"2.4";"lots of text";"blabla"
"sometext";"30";"10";"1.0";"lots of text";"blabla"
"somemoretext";"30";"10";"1.0";"lots of text";"fooooooo"

【问题讨论】:

  • 这似乎需要多长时间?您预计需要多长时间?
  • 我从未使用提到的数据量完成它。但是在 1500 万行上,这需要几个小时。我已经研究了所有标准的服务器优化内容(work_mem 等),所以我采用了不同的方法来获得相同的结果。
  • 我已编辑问题以包含示例数据。

标签: postgresql normalization etl star-schema fact-table


【解决方案1】:

您最后省略了一些细节,但我认为不一定有问题。没有证据表明所有字符串实际上都与所有其他字符串进行了比较。如果您进行连接,PostgreSQL 可以很好地选择更智能的连接算法,例如哈希连接,它可能会为您提供与您在 MySQL 解决方案中实现的相同的哈希。 (再一次,你的细节是模糊的。)

【讨论】:

  • 感谢您的回答。我现在更新了问题,缺少详细信息。
【解决方案2】:

想象一下可怕的表现 我通过将所有字符串与所有字符串进行比较得到 其他字符串多次。

当您这样做了一段时间后,您不再想象性能,而是开始衡量它。 “过早的优化是万恶之源。”

“十亿”对您意味着什么?对我来说,在美国,这意味着 1,000,000,000(或 1e9)。如果这对您来说也是如此,那么您可能正在查看 1 到 7 TB 的数据。

我目前的方法基本上是 运行以下查询以创建 维度表:

Create table dimension_table (id int, text varchar(1000), primary key(id));

如何将 100 亿行放入一个使用整数作为主键的表中?甚至可以说一半的行是重复的。当你做这个算术时,它是如何工作的?

别想了。先读一读。然后测试。

阅读Data Warehousing with PostgreSQL。我怀疑这些演示幻灯片会给你一些想法。

另请阅读Populating a Database,并考虑实施哪些建议。

按照“分而治之”的过程,使用一百万 (1e6) 行进行测试。也就是说,不要试图一次加载一百万;编写一个程序,将其分解成更小的块。运行

EXPLAIN <sql statement>

您曾说过您估计至少有 99% 的重复行。从广义上讲,有两种方法可以摆脱欺骗

  1. 在数据库中,不一定与您用于生产的平台相同。
  2. 在数据库之外,在文件系统中,不一定与您用于生产的文件系统相同。

如果您仍然有加载的文本文件,我会考虑首先在数据库之外尝试。这个 awk one-liner 将从每个文件中输出唯一的行。这是相对经济的,因为它只对数据进行一次传递。

awk '!arr[$0]++' file_with_dupes > file_without_dupes

如果您确实有 99% 的受骗者,那么到此过程结束时,您应该将 1 到 7 TB 减少到大约 50 GB。而且,完成此操作后,您还可以为每个唯一行编号并创建一个制表符分隔的文件,然后再将其复制到数据仓库中。那是另一个单行:

awk '{printf("%d\t%s\n", NR, $0);}' file_without_dupes > tab_delimited_file

如果您必须在 Windows 下执行此操作,我会使用 Cygwin

如果您必须在数据库中执行此操作,我会尽量避免使用您的生产数据库或生产服务器。但也许我太谨慎了。移动几 TB 是一件昂贵的事情。

但我会测试

SELECT DISTINCT ...

在使用 GROUP BY 之前。我也许可以为您对大型数据集进行一些测试,但本周可能不行。 (我通常不处理 TB 大小的文件。这很有趣。如果你可以等一下。)

【讨论】:

  • 我的表现很糟糕,我正在寻求解决具体问题的具体建议。 raw_fact_table 没有主键的整数。只有维度表,因为 fact_table 中有 99.XX% 的重复项。我已经实施了您发送给我的链接中的所有建议。
  • “ETL”中的“T”正在杀死你。 99% 的重复意味着您的目标是大约 100,000,000 行。我将编辑我的答案。
  • 为了简化示例,我只提到 raw_fact_table 有一个文本列。实际上它有十几个,因此您删除重复项的方法不起作用,不过感谢您指出这一点。我会更新这个问题。 raw_fact_table 也有整数值和双精度值。
  • @David:我写的 awk 单行代码仍然有效。它将整行视为关联数组的键。因此,无论列数如何,它都只会输出唯一的行。
  • 但是,根本不会有唯一的行。每列中只有重复,永远不会有整行重复。
【解决方案3】:

我看到了解决您问题的几种方法 PostgreSql中有md5函数 md5(string) 计算字符串的 MD5 哈希,以十六进制返回结果

insert into dimension_table (select null, md5(text), text from raw_fact_table group by text)

也将 md5 字段添加到 raw_fact_table select id into fact_table from dimension inner join raw_fact_table on (dimension.md5 = raw_fact_table.md5);

MD5 索引也可能有所帮助

或者您可以在加载数据时即时计算 MD5。 例如,我们的 ETL 工具 Advanced ETL 处理器可以为您完成。 此外,它可以同时将数据加载到多个表中。

我们的网站上有许多在线教程 例如这个演示加载缓慢变化的维度

http://www.dbsoftlab.com/online-tutorials/advanced-etl-processor/advanced-etl-processor-working-with-slow-changing-dimension-part-2.html

【讨论】:

  • 我不相信在运行 COPY FROM 时可以运行计算 MD5(这是加载数据的推荐方式)。如果这意味着您的工具不使用 COPY FROM,那么我相信它是无用的,因为没有它的加载将需要很长时间。我不得不说我对无代码 ETL 解决方案非常怀疑。只要我只需要做标准的东西就可以了,但是如果我遇到特殊情况的问题,我没有代码可以依靠。
  • 完全同意 COPY FROM 是将数据加载到 PostgreSQL 中的最快方法。这就是我们在 Advanced ETL Processor 内部使用它的原因。来自 PostgreSQL 文档:COPY TABLE_NAME FROM STDIN(STDIN 指定输入来自客户端应用程序。)
  • 我们尽最大努力让它尽可能快。对于每个数据库,我们都使用快速加载数据的方式。 (oracle 的直接/常规路径,SQL Server 的 bcp,PostgreSQL 的 copy from 等)我们实际上打印了关键代码并标记并消除了所有无效部分。比我们使用分析器并进一步优化性能。我们不断地进行改进。 (查看我们的支持论坛并注意解决问题或引入新功能所需的时间,而不是与大玩家进行比较)。
  • 我们的软件并不臃肿(企业版只有28M)它直接与16个数据源和17个数据目标一起工作。它不使用大量内存,内存使用与文件/表大小无关(OLE DB 源除外) 与 SSIS/DTS/ODBC/OLEDB 不同,它始终与 Excel 一起正常工作。它可以使用文件系统作为数据源并将文件加载到 blob 字段中。它可以将 blob 字段保存到单独的文件中它可以处理电子邮件并使用 POP3 服务器作为数据源它可以发送电子邮件并使用 SMTP 服务器作为数据目标。我可以继续,但我想你明白了。
  • ETL 软件非常难写。我的意思是你认识任何对 MS Sql Server、Oracle、MySql、PostgreSQL、Firebird、Interbase、SQLite、POP3、SMTP、XML、Excel、Access、DBF、Foxpro、ODBC、OLE DB 的底层 API 有深入了解的人吗? FTP、SFTP、FTPS、HTTP、SSL?我愿意。
【解决方案4】:
-- add unique index
CREATE UNIQUE INDEX uidx ON dimension_table USING hash(text);
-- for non case-sensitive hash(upper(text))

尝试哈希(文本);和 btree(text) 看看哪个更快

【讨论】:

    【解决方案5】:

    只问问题: - 是否需要分 1 步或 2 步转换您的数据? - 转换时可以修改表格吗?

    运行更简单的查询可能会提高您的性能(以及执行此操作时的服务器负载)

    一种方法是:

    1. generate dimension_table(如果我理解正确,你不会有性能问题)(可能有一个额外的临时布尔字段...)
    2. 重复:从维度表中选择一个以前未选择的条目,从包含它的raw_fact_table中选择每一行并将它们插入到fact_table中。将维度表记录标记为完成,然后下一步...您可以将其编写为存储过程,它可以在后台转换您的数据,占用最少的资源...

    或者另一个(可能更好):

    1. 从 raw_fact_table 和一个维度_id 创建 fact_table 作为每条记录。 (因此包括 dimension_text 和 dimension_id 行)
    2. 创建维度表
    3. 为 fact_table 创建一个插入后触发器,该触发器:
      • 在 fact_table 中搜索 dimension_text
      • 如果未找到,则在维度表中创建新记录
      • 将 dimension_id 更新为此 id
    4. 在一个简单的循环中,将 raw_fact_table 中的每条记录插入到 fact_table 中

    【讨论】:

    • 感谢您的建议。我没有指定这一点,但我唯一关心的是尽可能快地处理所有数据,因此在后台运行某些东西在我的设置中没有意义(我知道在其他情况下这是非常明智的)。第二种方法的问题是触发器不会在 COPY FROM 上触发。所以我认为没有理由触发。尽管使用游标,您的方法仍然非常有效。我虽然不确定性能:stackoverflow.com/questions/4776127/…
    • 关于你的第一种方法,我没有计时生成维度表的性能(我应该这样做)。我喜欢你用另一种方式思考的方法。我将使用基于您的方法创建新评论。
    • 创建表 dimension_table (id 序列号, text varchar(1000), raw_fact_table_id bigint[], 主键(id));----------------- -- 插入到 dimension_table (text, raw_fact_table_id) (select text, array_agg(raw_fact_table.id) from raw_fact_table group by text);之后,需要找到一种方法来根据 raw_fact_table_id 中的 id 更新 raw_fact_table。你怎么看?
    • 看起来不错。在此之后您可以轻松地更改原始表(添加列,然后更新),或者您可以创建一个连接表(您不需要更改原始表),从而解决了问题。 (使用未嵌套)
    • 我现在尝试了这个方法,它比原来的方法长了大约 7 倍。我运行更新的方式是从包含数组的维度表中获取一行,然后运行 ​​update raw_fact_table set text = '3' where id in(1,2,3,4,5,6,7,8,9 ,10,11,12,13,14,15,16,17);数组被解析为数字。特别是如果一列中的重复项很少(比如说只有 70%),那么需要运行大量更新查询。
    猜你喜欢
    • 2020-08-11
    • 1970-01-01
    • 2015-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-28
    • 2018-12-24
    • 1970-01-01
    相关资源
    最近更新 更多