表访问方法:PostgreSQL 中数据更新的处理方式
作者:Cary
前言
本文将详细探讨 PostgreSQL 如何处理更新操作。在 PostgreSQL 中,成功的更新可以被视为"插入一条新记录",同时"标记旧记录为不可见",这是因为 PostgreSQL 使用了 MVCC 技术。这个过程听起来简单,但实际上有许多因素需要考虑,以确保更新的成功。
涉及的 API
在执行更新操作时,主要涉及两种表访问方法的 API:
tuplefetchrow_version()
:此函数用于查找给定 TID 的元组的最新版本。我们需要使用给定的 TID 查找特定的元组进行更新。此外,如果适用,可以提供一个快照结构来执行可见性检查。该函数会提取元组并将其转换为 Tuple Table Slot,如果成功提取元组,则返回 true,否则返回 false。tuple_update()
:这是处理元组更新请求的主要处理函数。此函数会接收多个参数来执行更新操作:- 旧元组的 TID:待更新的旧元组的位置
- 新元组(以 Tuple Table Slot 表示):PostgreSQL 将其转换为 HeapTuple 进行更新
- 命令 ID 和快照:用于对待更新的旧元组执行可见性检查
更新流程
在执行更新操作之前,PostgreSQL 会进行一系列的检查和考量。此过程如下图所示:
1. 确定更新的列
主更新流程执行的第一个检查是确定需要更新的列,特别是要确认是否更新了标识键列(例如主键、索引键或分区键)。因为如果标识键列未被更新,可能无需为了执行更新操作而获取独占锁。此外,对于逻辑复制中的副本标识列,还需要进行额外的处理,以确保在 WAL 文件中记录足够的信息,以便逻辑订阅者能够识别出哪一行已被更新。
2. 确定元组是否可更新
这是关于并发控制的关键步骤。PostgreSQL 是一个多进程系统,一个元组可能会同时被多个客户端连接更新。当然,PostgreSQL 不允许一个元组同时被多个客户端更新,因此在继续操作之前,我们需要确保没有其他客户端正在更新同一个元组。这可以通过查看当前旧元组头部的 xmax
值(修改该元组的事务 ID)来检查,并查找 CLOG
或其提示位(hintbit)标志来确定 xmax
值是否已提交。在调用 HeapTupleSatisfiesUpdate()
后,有以下几种情况需要考虑:
- 元组不可见:这意味着另一个客户端后端已经更新了该元组(将其标记为不可见)并且已提交了事务。如果是这种情况,我们无法进行更新,因为根据快照,已经没有可更新的内容了。因此,系统将在此处报错。
- 元组正在被更新:这意味着另一个客户端后端已经更新了该元组(将其标记为不可见),但尚未提交或回滚事务。如果是这种情况,我们必须在此处等待,直到其他客户端的事务提交或中止。系统将进入等待循环,同时执行死锁检测,以确保我们正在等待的事务 ID 没有在等待我们完成(交叉等待,即死锁)。
- 元组可更新:这意味着该元组未被其他客户端后端更新,我们可以继续对其进行更新。
3. 准备新的元组头部
一旦我们确认可以更新旧元组,PostgreSQL 将开始准备新元组,将其从元组表槽(Tuple Table Slot)格式转换为堆元组(HeapTuple)格式。然后填写必要的头部信息,并准备插入操作。
4. TOAST 检查
一旦我们准备好了新的堆元组(HeapTuple)结构,就需要检查其大小是否适合更新期间给定的缓冲页剩余空间。我们需要确认新元组是否可以放入给定缓冲页的剩余空间中。如果可以,则无需执行额外操作,直接进入下一阶段处理。如果给定的缓冲页空间不足以容纳这个新的堆元组,那么我们需要执行 TOAST 操作。TOAST 是 PostgreSQL 中的一种技术,用于将大型数据分解为较小的块并以分布式方式存储。系统将调用 heap_toast_insert_or_update()
来完成 TOAST 操作,并生成一条 WAL 记录,表明在此处执行了 TOAST 操作,且在使用该值时需要进行 DE-TOAST 操作。
5. 从旧元组中提取副本标识
此操作仅仅是从旧元组中提取副本标识(如主键、索引键等),以便将这些附加信息包含在 WAL 段中,从而准确告知逻辑复制订阅者具体是哪一行被更新了。如果没有这个副本标识,逻辑复制订阅者只会收到某一行被更新为新值的通知,但无法确定具体是哪一行被更新了。
6. 页面设置为可修剪
此操作主要是对缓冲页的头部进行修改,以表明该页面将包含一个死元组,因为我们正在对其执行更新操作。这一标记主要是供 VACUUM 进程查看,用于判断一个页面是否完全可见(即页面内的所有元组都可见)或可修剪(即页面内存在需要清理的死元组)。
7. HOT(Heap Only Tuple) 更新
这是 PostgreSQL 采用的一种优化手段,用于在条件合适的情况下"节省索引元组"。之所以称为仅元组堆更新,是因为它字面上意味着只有堆元组,而没有直接关联的索引元组,即使在该表上创建了索引。
触发 HOT 更新以"节省索引元组"的条件如下:
- 没有索引列被更新。
- 新的元组可以插入到与旧元组相同的页面中。
如果满足上述条件,PostgreSQL 会将新的元组插入页面,并在页面内将旧元组与新元组"链接"在一起。与旧元组关联的索引元组仍然指向旧元组,但由于我们已将旧元组和新元组链接在同一页面内,我们仍然可以通过先定位到旧元组,然后沿着链接找到新元组。这样,PostgreSQL 就不需要创建一个新的索引元组来直接指向新元组,从而节省了一些潜在的元组空间。
8. 关系放入堆元组
在完成上述所有检查后,我们现在可以将新的元组放入指定的页面,并正确设置元组头部信息和 HOT 标志。
9. 将旧元组标记为不可见
新元组插入后,我们现在可以通过将旧元组的 xmax 值设置为当前事务 ID,设置其提示位(hintbit)和适当的 HOT 标志,将旧元组标记为不可见。
10. 标记缓冲区为脏页
这是一个缓冲区管理器例程,用于通知管理器当前页面内容已被修改。因此,在需要置换缓冲区页面前,必须先将该页面的修改内容刷新到磁盘。周期性的检查点进程会主动尝试优先将此脏页刷新到磁盘。
11. 缓存使堆元组无效
该机制用于通知所有活跃的后端进程,如果它们在其本地缓存中存储了刚刚被标记为不可见(已删除)的堆元组,则使这些元组失效。
12. 更新索引标志
在更新结束时,我们需要设置一个标志,告知执行器是否需要为新元组创建索引元组。在 HOT 的情况下,或者当表上没有创建索引时,我们通常会将其设置为 false。
总结
与插入和顺序扫描不同,更新操作在完成时需要更多的考虑。它不仅需要考虑并发控制,还必须考虑一系列优化措施、超大元组以及用于逻辑复制的副本标识。这些因素使得更新操作通常成为执行起来更为昂贵的操作。
关于 IvorySQL
lvorySQL 是由瀚高股份主导研发的一款开源的兼容 Oracle 的 PostgreSQL。IvorySQL 与 PostgreSQL 国际社区紧密合作,保持与最新 PG 版本内核同步,为用户提供便捷的升级体验。基于双 Parser 架构设计,100% 与原生 PostgreSQL 兼容,支持丰富的 PostgreSQL 周边工具和扩展,并根据用户需求提供定制化工具。同时,IvorySQL 4.0 提供更全面灵活的 Oracle 兼容功能,具备高度的 SQL 和 PL/SQL 兼容性能够为企业构建更加高效、稳定和灵活的数据库解决方案。
- 官网:https://www.ivorysql.org
- GitHub(欢迎点击 star 收藏哦):https://github.com/IvorySQL/IvorySQL
- 社群:VX 搜索"ivorysql_official" 添加小助理进群
本文由博客一文多发平台 OpenWrite 发布!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从湖仓分离到湖仓一体,四川航空基于 SelectDB 的多源数据联邦分析实践
导读:随着川航大规模数据导入需求增长,数据响应频繁出现卡顿,原 Hadoop + Apache Doris 的数据架构存在着涉及组件多、并发性不足、数据导入受限的痛点。经过综合选型对比,川航选择引入 SelectDB 建设湖仓一体大数据分析引擎,取得了数据导入效率提升 3-6 倍,查询分析性能提升 10-18 倍、实时性提升至 5 秒内等收益。 本文转录自吴乐(四川航空 信息技术部 大数据架构师)在 Doris Summit Asia 2024 上的演讲,经编辑整理。 业务背景 四川航空股份有限公司(以下简称:川航),自开航至今持续安全飞行 36 年,现运营全空客机队超过 200 架飞机,年运送旅客量超过 3000 万,航线覆盖亚洲、欧洲、北美洲、大洋洲和非洲,品牌价值超过 900 亿。 航空公司的业务具有业务系统繁多、数据交互复杂、实时性要求高三大特点。 业务系统繁多:航空公司业务覆盖航班调度、票务销售、旅客服务、机组管理、机务维修、财务结算等多个领域,运营跨国航班还需满足不同国家法规与合规要求,这就使得涉及的基础架构与数据库种类繁多。 数据交互复杂:多业务系统间数据交互具有复杂性,...
- 下一篇
当年我靠着这个回答,顺利应聘了架构师岗位
文章首发公众号『风象南』 我曾经在一家小企业担任技术一号位,由于公司的规模及业务发展缓慢与我个人对成长的强烈渴望之间产生了矛盾,于是我选择去市场上寻找新机会。 经过筛选,我把目标锁定在了一家做物联网方面的公司,选择的原因也是因为对我来说这是个全新的行业,里面一定有我之前没遇到过的技术挑战,而我选择投递的职位是架构师。 当时面试我的技术副总在与我沟通完所有技术问题后,他最后问了我一个问题 你认为做架构设计最难的是什么 ? 思考10秒钟,如果是你,你会怎么回答这个问题 我的回答 我认为,架构最难的就是做权衡取舍,在有限的资源、有限的时间等条件约束下如何选择符合当下最优的解决方案。 最终经过4轮面试,从3个候选人中顺利杀出(当然最终公司选择我的原因肯定也不是这一个回答,而是结合前面的技术面试综合后的结果),最终以架构师岗位入职公司。 今天,我想就这个问题再进行一些展开,把自己关于系统架构、设计方面的一些感悟进行一些简单梳理 那么,首先要知道什么是架构设计,基于个人理解先对架构设计下个定义 系统架构是指在特定环境下,通过合理的组织各个元素(如业务服务、数据库、缓存、消息队列等)及其之间的依赖关...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- MySQL8.0.19开启GTID主从同步CentOS8
- Hadoop3单机部署,实现最简伪集群
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Linux系统CentOS6、CentOS7手动修改IP地址