当前位置: 首页 > article >正文

表访问方法: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” 添加小助理进群

http://www.kler.cn/a/572261.html

相关文章:

  • SpringBoot获取YAML配置文件中的属性值(二):使用Environment环境组件读取值
  • Leetcode 二叉搜索树迭代器
  • SLAM文献之-DROID-SLAM: Deep Visual SLAM for Monocular, Stereo, and RGB-D Cameras
  • netframework 读取appsettings.json
  • 【练习】【链表】力扣热题100 160. 相交链表
  • CC++的内存管理
  • 网络安全ctf试题 ctf网络安全大赛真题
  • stm32(hal库)学习笔记-时钟系统
  • BS架构(笔记整理)
  • npm、Yarn 与 pnpm:选择最适合你的包管理工具
  • 【Android】 工具篇:ProxyPin抓包详解---夜神模拟器
  • 突破光学成像局限:全视野光学血管造影技术新进展
  • 深度学习-大白话解释循环神经网络RNN
  • 医药行业哪些招聘管理系统有AI功能?
  • 基于vue3和flask开发的前后端管理系统(一):项目启动准备
  • 鸿蒙5.0实战案例:基于webview拉起自定义键盘
  • 【车规芯片】如何引导时钟树生长方向
  • C#基础及标准控件的使用,附登录案例
  • navicat下载与安装【带布丁】
  • printf 与前置++、后置++、前置--、后置-- 的关系