7

想象一下 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"
4

5 回答 5

7

试想一下,通过将所有字符串与所有其他字符串进行多次比较,我得到的可怕性能。

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

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

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

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

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

不要想象。先读一读。然后测试。

阅读使用 PostgreSQL 的数据仓库。我怀疑这些演示幻灯片会给你一些想法。

另请阅读填充数据库,并考虑实施哪些建议。

按照“分而治之”的过程,用一百万 (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 大小的文件。这很有趣。如果你可以等一下。)

于 2011-01-23T04:29:22.940 回答
2

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

于 2011-01-03T12:02:27.017 回答
2
-- add unique index
CREATE UNIQUE INDEX uidx ON dimension_table USING hash(text);
-- for non case-sensitive hash(upper(text))

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

于 2011-01-25T06:24:52.197 回答
2

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

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

一种方法是:

  1. 生成维度表(如果我理解正确,你没有性能问题)(可能有一个额外的临时布尔字段......)
  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
于 2011-01-25T09:27:11.573 回答
1

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

插入维度表(选择空值,md5(文本),来自raw_fact_table的文本按文本分组)

将 md5 字段添加到 raw_fact_table 以及从维度内部连接 ​​raw_fact_table on (dimension.md5 = raw_fact_table.md5) 中选择 id 到 fact_table;

提交的 MD5 索引也可能有所帮助

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

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

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

于 2011-01-24T19:22:36.423 回答