在 Redshift 中更新整个表的正确方法,删除表 + 创建表与截断 + 插入表

Posted

技术标签:

【中文标题】在 Redshift 中更新整个表的正确方法,删除表 + 创建表与截断 + 插入表【英文标题】:Proper way of updating a whole table in Redshift, drop table + create table vs. truncate + insert into table 【发布时间】:2021-10-13 10:21:04 【问题描述】:

目前我有很多表,我必须更新它们所持有的信息,有时每天或每周更新一次。到目前为止,我一直在通过 DROP TABLE IF EXIST some_schema.some_table_name;CREATE TABLE some_schema.some_table_name AS ( SELECT ... FROM ... WHERE ...); 的组合来做到这一点,我想知道什么是“最佳实践”或这样做的正确方法。

我听说 Redshift 中的 INSERT 操作非常昂贵,所以我一直在避免使用它,但也许将 TRUNCATEINSERT 一起使用比删除和创建要好。

如何确认哪个选项更好?

我看过 Redshift 文档中的 this 文章,但我不确定这是否是最佳选择,因为我不仅可以删除记录,还可以保留和插入记录。

【问题讨论】:

【参考方案1】:

如果您希望完全擦除表格并替换数据,那么您遵循的一般模式很好。但是,您应该做一些事情来让事情变得更安全/更好。

有 3 种模式可以做到这一点,其中一种显然是性能最低的。它们是删除/插入、截断/插入和删除/插入。从性能的角度来看,这些删除/创建/插入不是您想要做的。此过程使表中的所有行无效(而不是删除它们)并添加新的有效行。这会使桌子的大小增加一倍,浪费空间,并且需要吸尘。这种方法的唯一优点是它没有其他方法的缺点,但这仅在某些情况下很重要。只有在必要时才采用这种方法。

截断/插入速度很快,并保持与原始表相同的表 ID。因为 truncate 对表的块进行操作(取消链接),所以速度很快,但在管理所有块链接时会有一些小的开销。由于表定义未更改,所有 DDL 都保持定义,依赖视图可以继续指向表。 truncate 的缺点是它会强制 COMMIT 发生,这意味着在用新数据重新填充表之前,数据库的其他用户可以看到一个空表。这可能会在这些窗口期间导致不正确的结果。不好。

最后是删除/创建/插入。在理想情况下,这种方法比截断略快(非常轻微,仅适用于大表)。它只是扔掉旧块。设置新表(同名)有一些额外的成本,因此除非表很大,否则截断和删除的速度大致相同。由于 Drop 可以在事务块内,因此第三方不会看到空表(如果操作正确)。这种方法的缺点是旧表和新表是完全不同的表(不同的 oid)——它们只是碰巧有相同的名称。这意味着任何依赖(常规)视图也需要被删除和重新创建。此外,由于该表正在“消失”,因此在表的所有使用完成之前,事务的提交无法完成。当有人在他们的板凳上留下交易并回家过夜时,这将成为一个大问题。由于需要重新创建表,因此您的进程需要知道表的完整且正确的 DDL。

希望这能让您了解何时使用这些不同的方法。我看到在您当前的代码中可能会更好的两件事 - 1)您没有使用事务块(据我所知),因此有一个窗口当其他人看到表不存在或为空时。这对您可能很重要,也可能不重要,但请注意。 2)“创建表为”没有以高性能结构定义表的 DDL(并且可能不正确)。您应该始终完全指定您的永久表。 Sort 和 Dist 键与 varchar 长度、数据类型等一样重要。这是一个等待引爆的定时炸弹。

根据请求提供删除/创建/插入示例:

正如我所提到的,这种方法可能会出现锁依赖问题,因此我喜欢对这条路径使用“交换和删除”方法。这使得新信息在“交换”时对用户可见,因此即使“丢弃”被阻止,事情也会按时发布。这并不能消除锁定风险,因为锁定仍然可以阻止进程(会话)完成,它只是使新数据在您追捕罪犯时可见(发布)。

(请注意,要正确执行事务,您需要确保没有将额外的 COMMIT 插入到进程中。这可能发生在配置为“自动提交”模式的工作台上。)

Create table new_table ( ... ) ...;  -- make the new table but with a different name (and unique from other tables) than the existing table
Insert into new_table ... ; -- put the desired data into the new table
Analyze new_table;  -- to ensure metadata is up to date
Begin; -- start transaction
Alter table perm_table rename to old_table; -- rename existing table
Alter table new_table rename to perm_table; -- complete the swap
Commit; -- publish the new data for all to see but transactions still using the original data can keep doing so
Drop table old_table;  -- remove the old data to free up space
Commit; 

这个过程只是一个例子。有时您希望将旧版本的表保留一段时间(历史记录/错误恢复),以便为旧数据添加日期标记并有一个单独的过程来释放空间。这也有助于防止杂散锁阻塞工作——只有清理过程才会停止。您还可以在流程中重新创建视图,以便在同一事务中更新这些视图。以此类推。

【讨论】:

谢谢比尔,这就是我正在寻找的解释。关于您的答案的一些问题,以防万一这些澄清了一些需求。我不希望完全擦除一个表并替换数据,事实上,我总是用与我刚刚删除的表相同的结构重新创建表,我真正想要实现的是“忘记”他们拥有的数据,并以最有效或“最佳实践”的方式再次将“当前”或最新数据放入表中。另外我想知道我必须做什么才能正确创建交易块? 我认为我的“擦除并替换”和您的“忘记并再次放置”对于数据库来说是一回事。我认为您是从数据的角度说的,但如果我遗漏了特定的数据库最终状态差异,请告诉我。您想“清空桌子并重新装满”。 “最佳方式”取决于数据库的要求。我不太了解您的限制,这就是为什么我列出了差异/优点/缺点。我将添加一个示例,说明我是如何完成删除/插入的,但它可能不适合您(将显示交易)。 DDL 定义不是从源表继承的,尽管某些信息可能会出现。如果选择是“简单的”,那么 dist 和 sort 键将被继承,否则将从查询计划中推断出来。在 CTAS 中明确显示 dist 和 sort 键是好的,并且可以防止一些问题。列类型将由查询计划定义,因此将再次成为“简单”选择的源表定义。永远不会保留列编码,并将根据数据类型/其他 DDL 信息进行硬编码。约束永远不会被继承。 CTAS 没有任何问题,只是要注意它的作用和不作用。我提出的担忧是长期支持代码库。来自未来可能发生变化的选择语句的隐式 DDL 可能会在未来产生难以发现的问题。如果您真的只是想让该表与另一个现有表具有相同的定义,我建议创建 TABLE LIKE。但是,这可能会产生另一种难以找到的依赖关系。我更喜欢将我所有的表 DDL 放在一个签入的存储库中,并让我的代码使用从这个 repo 中提取的显式 DDL 定义。 没有视图也没有其他人在看表格肯定有帮助:)【参考方案2】:

我认为您将需要使用更新命令。我知道下垂表是一个冒险的举动,因为您可能会丢失数据库中的所有数据。

Update some_table_name s set
   s.Id="whatever you want to update",
   s.Name="whatever you want to update",
   s.LastName="whatever you want to update",
   s.OtherTableColumn="whatever you want to update"
From
   some_table_name s

在上面的代码中,我假设您的表格包含列(1-Id、2-Name、3-LastName、4-OtherTableColumn)。如果您有更多或更少的列,那么我会相应地调整。

我还会为此(以及每个表)编写一个更新程序,因此如果您需要频繁更新,您只需使用该程序即可;我认为它更快。以下是我的程序:

Create Proc sp_UpdateSome_table_name
   @Id int,
   @name nvarchar(255),
   @lastname nvarchar(255),
   @OtherTableColumn int
AS
BEGIN
Update s some_table_name
       
   s.Name="whatever you want to update",
   s.LastName="whatever you want to update",
   s.OtherTableColumn="whatever you want to update"
From
   some_table_name s
Where
   s.Id=@Id
END

您希望确保表中的每一列都在过程中使用正确的数据类型进行了定义。例如,我在上面假设@Id 是 int,Name 是 nvarchar(255) 等。如果你想让自己在更新时不要在某些表列中输入任何数据(允许 null),那么在数据类型之后你可以写 Null;例如如果你写@Id int Null,那么你可以更新为null;但如果您不确定这是什么,请暂时忽略这句话。

一旦你确定上面的段落是好的(数据类型是正确的),然后选择整个过程然后执行(F5)。这将存储此过程。

然后我会在每次你想更新你的表格时编写如下所示的过程:

Exec sp_UpdateSome_table_name 1,John,Smith,77

如果您突出显示上述命令并执行 (f5) 它,它将更新 Id=1 的表,并将其命名为 John、姓氏 Smith 和其他列 77,而不是之前的列。如果 Id=1 的表中没有数据,则可以执行。

请记住,代码的最后一行可能没有逗号。上面的代码写的很正确,只是指出来,因为你可能习惯用逗号。

【讨论】:

以上是关于在 Redshift 中更新整个表的正确方法,删除表 + 创建表与截断 + 插入表的主要内容,如果未能解决你的问题,请参考以下文章

从 redshift 中删除外部表的所有分区

如何使用 Python 更新、插入和删除 Redshift 数据库?

删除 Redshift 中的循环

Redshift 表所有权和删除查询

如何在 Redshift(亚马逊)中更新 UDF?

Redshift:使用来自另一个表的随机数据更新或插入列中的每一行