如何在PostgreSQL中更新大表

本文来源:www.codacy.com/blog/how-to…sql

在Postgres中更新大型表并不像看起来那样简单。若是您的表包含数亿行,您将发现很难及时进行简单的操做,例如添加列或更改列类型。数据库

在不停机的状况下进行这类操做是一个更大的挑战。在这篇博客文章中,我将尝试概述一些策略,以在管理大型数据集的同时最大程度地减小表不可用性。安全

通常准则

当您更新列中的值时,Postgres将在磁盘中写入一个新行,弃用旧行,而后继续更新全部索引。此过程等同于INSERT加上每一行后再DELETE,这会占用大量资源。bash

除此以外,须要更新大表时还应了解的事项列表:并发

  • 从头开始建立新表比更新每一行要快。顺序写比稀疏更新快,而且最后不会出现死行。
  • 表约束和索引严重延迟了每次写入。若是可能,应在更新运行时删除全部索引,触发器和外键,并在最后从新建立它们。
  • 添加没有默认值的可空列是一种廉价的操做。写入列的实际数据是昂贵的部分。
  • 更新行时,不会重写存储在TOAST中的数据
  • 从Postgres 9.2开始,在某些数据类型之间进行转换不须要重写整个表。例如:从VARCHAR(32)转换为VARCHAR(64)。

考虑到这一点,让咱们看一些能够用来有效更新表中大量数据行的策略:ide

增量更新

若是您可使用例如顺序ID对数据进行细分,则能够批量更新行。因为您只须要保持较短期的锁定,所以能够最大化表的可用性。若是添加新列,则能够将其临时设置为可为空,而后开始逐渐用新值填充它。post

这种方法的主要问题是性能,这是一个很是缓慢的过程,由于就地更新成本很高。在迁移期间,它可能还须要更复杂的应用程序逻辑。性能

建立一个新表

更新大表的最快方法是建立一个新表。优化

若是能够安全地删除现有表,而且有足够的磁盘空间,则执行更新的最简单方法是将数据插入到新表中,而后对其进行重命名。如下是此操做的基本执行脚本:spa

create table user_info_copy (LIKE user_info INCLUDING INDEXES INCLUDING COMMENTS);

INSERT INTO user_info_copy
SELECT user_no, idcard_no, real_name, bankcard_no, bind_mobile
     , false, bind_status, user_identity, create_time, creator
     , edit_time, editor, is_del, VERSION, customer_id
     , id_card_type, source_id, platform_no, one_passport_no, bank_code
FROM user_info;

drop TABLE user_info;

alter table user_info_copy rename to user_info;
复制代码

从新建立现有表

若是因为不想从新建立视图或因为其余限制而不能删除原始表,则可使用临时表保存新值,截断旧表并在那里重写数据。当您有未决的写请求时,此方法也有一些优势,如咱们将在下一部分中看到的。

若是您的表能够容纳在内存中,则应在此事务期间增长temp_buffers属性。使用RAM代替磁盘来存储临时表将明显提升性能:

SET temp_buffers = 3000MB; ----相应地更改此值

# 建立临时表
CREATE TABLE temp_user_info(  
   user_no BIGINT,  
   PRIMARY KEY( user_no )  
);
# 若是须要提速能够从表中删除索引
# 复制数据到临时表中
insert into temp_user_info select user_no from user_info;

# 改变表结构,好比须要添加新列
TRUNCATE user_no;
# 执行插入列字段语句
# 再把数据反写到user_info表
复制代码

处理并发写入

即便进行了上述优化,从新建立表仍然是缓慢的操做。若是您正在实时数据库中运行查询,则可能须要处理并发写入请求。

最简单的方法是在事务期间在表上强制使用SHARE LOCK, 语句以下

LOCK TABLE user_info IN SHARE MODE;
复制代码

若是花费太长时间,全部写请求将一直等到锁释放或超时为止。若是未删除原始表,则一旦事务结束,将执行未超时的请求。请注意,即便使用相同的名称建立新表,请求仍将失败,由于它们使用表OID

根据写请求的性质,您还能够建立自定义规则来存储对表所作的更改。例如,您能够设置一个规则,以在开始数据迁移以前记录已删除的行:

CREATE RULE deleted_rule AS ON DELETE
TO tbl
DO INSERT INTO tbl_deletes VALUES
(
  OLD.id
);
复制代码

迁移结束时,您只需从tbl_deletes中读取ID,而后在新表上将其删除。可使用相似的方法来处理其余类型的请求。

结论

一旦达到必定大小,曾经瞬时的操做可能须要几个小时来准备和执行。我的实验结论:

  • 用存储过程批量更新 560w , 1455秒结束
  • 用复制表更名方法操做 560w数据, 120秒左右就结束了;

点我拼团

点我抽奖

相关文章
相关标签/搜索