向具有 4000 万条记录的表添加多列主键

Posted

技术标签:

【中文标题】向具有 4000 万条记录的表添加多列主键【英文标题】:Adding a multi-column primary key to a table with 40 million records 【发布时间】:2013-06-13 13:48:01 【问题描述】:

我正在维护一个数据库,该数据库存储不同网络之间的数据传输信息。本质上,每个数据传输都会被记录下来,并且在每个月底我运行一个 perl 脚本,将日志文件加载到数据库中的一个表中。我没有设计 perl 脚本或数据库模式。这是在我开始从事这个项目之前完成的。

我使用this 链接来检索表的主键(usage_detail 是表的名称),但它什么也没给我。由于表中有很多记录,因此跟踪重复项并不容易。我们遇到了加载大量重复项的问题(因为错误脚本会为每次传输记录日志,但那是针对另一个主题),最终不得不在修复日志文件后删除最新加载并重新加载所有新加载.正如您可能已经猜到的那样,这是愚蠢而乏味的。

为了解决这个问题,我想在表中添加一个主键。由于几个原因,我们不想为主键添加一个全新的列。查看字段后,我找到了一个多列主键。基本上它包括:传输开始时间戳、传输结束时间戳、传输文件的名称(也包括整个路径)。似乎极不可能有两条记录具有相同的字段。

这是我的问题: 1)如果我在表中添加这个主键,表中可能已经存在的任何重复项会发生什么?

2) 我如何将这个主键实际添加到表中(我们使用的是 PostgreSQL 8.1.22)。

3) 添加主键后,假设在加载脚本运行时它会尝试加载副本。 PostgreSQL 会抛出什么样的错误?我能在脚本中捕捉到它吗?

4) 我知道您没有太多关于加载脚本的信息,但是鉴于我提供的信息,您是否预见到脚本中可能需要更改的内容?

非常感谢任何帮助。 谢谢。

【问题讨论】:

【参考方案1】:

使用串行列

您的计划是为 4000 万 (!) 行添加一个不必要的巨大索引。你甚至不确定它是否是独一无二的。我强烈反对这种做法。添加一个serial 列并完成它:

ALTER TABLE tbl ADD COLUMN tbl_id serial PRIMARY KEY;

这就是你需要做的。其余的会自动发生。手册或这些密切相关的答案中的更多内容:PostgreSQL primary key auto increment crashes in C++Auto increment SQL function

添加serial 列是一次性操作,但代价高昂。必须重写整个表,在操作期间阻止更新。最好在非工作时间没有并发负载的情况下完成。我引用the manual here:

添加具有非空默认值的列或更改类型 现有列将需要整个表和索引 重写。 [...] 表和/或索引重建可能需要很长时间 一张大桌子的时间;并且暂时需要尽可能多的 作为磁盘空间的两倍。

由于这有效地重写了整个表,您不妨创建一个带有序列 pk 列的新表,插入旧表中的所有行,让序列填充其序列中的默认值,删除旧的并重命名新的。更多这些密切相关的答案:Updating database rows without locking the table in PostgreSQL 9.2Add new column without table lock?

确保你所有的 INSERT 语句都有一个目标列表,那么额外的列就不会混淆它们:

INSERT INTO tbl <b>(col1, col2, ...)</b> VALUES ...

不是:

INSERT INTO tbl VALUES ...

serial 使用 integer 列(4 个字节)实现。 主键约束通过唯一索引和涉及的列上的 NOT NULL 约束来实现。 索引的内容存储起来很像表。单独需要额外的物理存储。有关物理存储的更多信息,请参阅此相关答案:Calculating and saving space in PostgreSQL

您的索引将包括 2 个时间戳(2 x 8 字节)加上一个冗长的文件名,包括。路径(~ 50 字节?)这将使索引大约 2.5 GB 更大(40M x 60 .. 一些字节)并且所有操作都变慢。

处理重复项

如何处理“导入重复”取决于您如何导入数据以及如何准确定义“重复”。

如果我们谈论的是COPY 语句,一种方法是使用临时临时表并在INSERT 命令中使用简单的SELECT DISTINCTDISTINCT ON 折叠重复项:

CREATE TEMP TABLE tbl_tmp AS
SELECT * FROM tbl LIMIT 0;     -- copy structure without data and constraints

COPY tbl_tmp FROM '/path/to/file.csv';

INSERT INTO tbl (col1, col2, col3)
SELECT DISTINCT ON (col1, col2)
       col1, col2, col3 FROM tbl_tmp;

或者,也禁止与已经存在的行重复:

INSERT INTO tbl (col1, col2, col3)
SELECT i.*
FROM  (
   SELECT DISTINCT ON (col1, col2)
          col1, col2, col3
   FROM   tbl_tmp
   ) i
LEFT   JOIN tbl t USING (col1, col2)
WHERE  t.col1 IS NULL;

温度。表会在会话结束时自动删除。

但正确的解决方法是首先处理产生重复的错误根源。

原问题

1) 如果所有列都有一个重复项,则根本无法添加 pk。

2) 我只会用 5 英尺长的杆子触摸 PostgreSQL 数据库 8.1 版。它非常古老、过时且效率低下,不再受支持,并且可能有许多未修复的安全漏洞。 Official Postgres versioning site.@David 已经提供了 SQL 语句。

3 & 4) 重复密钥违规。 PostgreSQL 抛出错误也意味着整个事务被回滚。在 perl 脚本中捕获它不能使其余的事务通过。例如,您必须使用 plpgsql 创建一个服务器端脚本,您可以在其中捕获异常。

【讨论】:

欧文,一个问题。如果 OP 没有唯一约束,他将如何解决“我们遇到了加载大量重复项的问题”。序列字段只会给每个 dup 一个唯一的“id”,但不会阻止它们被加载。 @DavidS:连续专栏对那些骗子本身没有帮助。如何解决重复问题取决于如何准确定义“重复”以及这些导入是如何发生的。我在答案中添加了一些内容。 我真的很喜欢临时表的建议并选择 distinct。 这真的很有帮助,非常感谢。我没有意识到添加具有表中已经存在的列的主节点需要更多空间。这个序列像标准整数吗?就像我会记录 1 到记录 40000000 吗?如前所述,我会敦促适当的人升级 PosgreSQL 版本。 @user1338584:我写了更多关于 pk 约束、添加列和物理存储的内容。要了解串行列,请点击我提供的手册的链接。我又加了一些。【参考方案2】:
    您将无法添加重复项。您需要先删除重复项。 ALTER TABLE foo 添加约束 foo_pkey PRIMARY KEY(fld1, fld2); PostgreSQL 会给你一个 SQL 状态代码的错误:23505。我 对perl一无所知,但我想你可以陷阱 这个。 再说一次,对 perl 一无所知,但我认为你可以 捕获错误,然后由您决定如何处理 它。

注意:您使用的是不受支持的 PostgreSQL 版本(您可能应该升级)。我什至无法在 SqlFiddle 上对此进行测试。所以,所有答案都是基于 PosgreSQL 9.1 给出的

【讨论】:

当你说我不能添加PK时你到底是什么意思?我的意思是,如果我确实在表内运行带有重复项的 ALTER TABLE,它只会给我一个错误?至于 PosgreSQL 版本,确实不在我手上,但我一定会让正确的人知道升级。 是的;你会得到一个错误。主键字段中的值必须是唯一的。注意:这是一件容易测试的事情。您可以创建一个临时表并插入一些记录,然后看看会发生什么。

以上是关于向具有 4000 万条记录的表添加多列主键的主要内容,如果未能解决你的问题,请参考以下文章

性能 - 使用 Spring JPA Data 搜索具有 2000 万条记录的表

即使使用 parallel(8) 提示,具有数百万条记录的表中的 Count(1) 也很慢

具有百万条记录的表中的Count即使有parallel提示也很慢

oracle中 主键和外键是啥意思?啥地方采用呢?

oracle中 主键和外键是啥意思?啥地方采用呢?

将数据插入具有主键的表(多列)中,该表具有除主键以外的数据的另一个数据