如何在PostgreSQL中更新大表

本文涉及的产品
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: 如何在PostgreSQL中更新大表

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

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

一般准则

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

除此之外,需要更新大表时还应了解的事项列表:

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

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

增量更新

如果您可以使用例如顺序ID对数据进行细分,则可以批量更新行。由于您只需要保持较短时间的锁定,因此可以最大化表的可用性。如果添加新列,则可以将其临时设置为可为空,然后开始逐渐用新值填充它。

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

创建一个新表

更新大表的最快方法是创建一个新表。

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

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秒左右就结束了;



相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
SQL 弹性计算 关系型数据库
HTAP数据库 PostgreSQL 场景与性能测试之 3.1 - (OLAP) 大表JOIN统计查询-10亿 join 1亿 agg
标签 PostgreSQL , HTAP , OLTP , OLAP , 场景与性能测试 背景 PostgreSQL是一个历史悠久的数据库,历史可以追溯到1973年,最早由2014计算机图灵奖得主,关系数据库的鼻祖Michael_Stonebraker 操刀设计,PostgreSQL具备与Oracle类似的功能、性能、架构以及稳定性。 PostgreSQL社区的贡献者众多
1980 0
|
关系型数据库 PostgreSQL
PostgreSQL 大表自动 freeze 优化思路
PostgreSQL 的版本冻结是一个比较蛋疼的事情,为什么要做版本冻结呢?因为PG的版本号是uint32的,是重复使用的,所以每隔大约20亿个事务后,必须要冻结,否则记录会变成未来的,对当前事务"不可见"。冻结的事务号是2 src/include/access/transam.h #def
5399 0
|
关系型数据库 分布式数据库 PolarDB
《阿里云产品手册2022-2023 版》——PolarDB for PostgreSQL
《阿里云产品手册2022-2023 版》——PolarDB for PostgreSQL
348 0
|
存储 缓存 关系型数据库
|
存储 SQL 并行计算
PolarDB for PostgreSQL 开源必读手册-开源PolarDB for PostgreSQL架构介绍(中)
PolarDB for PostgreSQL 开源必读手册-开源PolarDB for PostgreSQL架构介绍
399 0
下一篇
无影云桌面