作者: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 兼容性能够为企业构建更加高效、稳定和灵活的数据库解决方案。

本文由博客一文多发平台 OpenWrite 发布!

表访问方法:PostgreSQL 中数据更新的处理方式的更多相关文章

  1. Mysql单表访问方法,索引合并,多表连接原理,基于规则的优化,子查询优化

    参考书籍<mysql是怎样运行的> 非常推荐这本书,通俗易懂,但是没有讲mysql主从等内容 书中还讲解了本文没有提到的子查询优化内容, 本文只总结了常见的子查询是如何优化的 系列文章目录 ...

  2. 在PostgreSQL中使用oracle_fdw访问Oracle

    本文讲述如何在PostgreSQL中使用oracle_fdw访问Oracle上的数据. 1. 安装oracle_fdw 可以参照:oracle_fdw in github 编译安装oracle_fdw ...

  3. [转]SAP中找表的方法

    http://blog.chinaunix.net/uid-24063584-id-2642334.html 分类: 18种根据屏幕字段查找数据库表数据的技巧 帮助   18种根据屏幕字段查找潜在数据 ...

  4. Sql Server中的表访问方式Table Scan, Index Scan, Index Seek

    1.oracle中的表访问方式 在oracle中有表访问方式的说法,访问表中的数据主要通过三种方式进行访问: 全表扫描(full table scan),直接访问数据页,查找满足条件的数据 通过row ...

  5. 转:Sql Server中的表访问方式Table Scan, Index Scan, Index Seek

    0.参考文献 Table Scan, Index Scan, Index Seek SQL SERVER – Index Seek vs. Index Scan – Diffefence and Us ...

  6. .NET中微软实体框架的数据访问方法

    介绍 本文的目的是解释微软的实体框架提供的三种数据访问方法.网上有好几篇关于这个话题的好文章,但是我想以一个教程的形式更详细地介绍这个话题,这个教程对于开始学习实体框架及其方法的人来说是个入门.我们将 ...

  7. 深入理解为什么Java中方法内定义的内部类可以访问方法中的局部变量

    好文转载:http://blog.csdn.net/zhangjg_blog/article/details/19996629 开篇 在我的上一篇博客 深入理解Java中为什么内部类可以访问外部类的成 ...

  8. 局部内部类访问方法中的局部变量为什么加final

    转载:http://www.cnblogs.com/mjblogs/p/4971630.html 1)从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形 ...

  9. C#中显/隐式实现接口及其访问方法

    原贴地址: http://www.cnblogs.com/dudu837/archive/2009/12/07/1618663.html 在实现接口的时候,VS提供了两个菜单,一个是"实现接 ...

  10. PHP中的数组方法及访问方法总结

    一.数组操作的基本函数 数组的键名和值 array_values($arr);获得数组的值 array_keys($arr);获得数组的键名 array_flip($arr);数组中的值与键名互换(如 ...

随机推荐

  1. Qt编写地图综合应用18-地图模式

    一.前言 除了传统的街道图地图外,默认的一般都是街道图,还有卫星图.三维图等,其中又有叠加层,比如叠加路况图层和路网图层等,最近去了多家的地图官网看对应的api接口,总体上感觉现在都往2.5D或者3D ...

  2. GNU Make中CPPFLAGS和CXXFLAGS之间的区别

    GNU Make 是一个流行的构建工具,用于编译和链接源代码.在 GNU Make 中,CPPFLAGS 和 CXXFLAGS 都是用于指定编译器选项的变量.它们之间的主要区别在于它们分别适用于 C ...

  3. IM跨平台技术学习(二):Electron初体验(快速开始、跨进程通信、打包、踩坑等)

    本文由蘑菇街前端技术团队分享,原题"Electron 从零到一",有修订和改动. 1.引言 在上篇<快速了解新一代跨平台桌面技术--Electron>,我们已经对Ele ...

  4. 使用CRM REST Builder的Predefined Query在js结合FetchXML语句进行查询

    一般情况下使用拓展工具RESTBuilder编辑器,可以很方便的进行操作js中增删改查均能实现,但在某些较为特殊的场景下,需要根据条件去拼接查询过滤条件的,使用编辑器生成的代码无法实现,需要结合使用f ...

  5. Solution Set - “也许我们早已经共鸣在那约定之地”

    目录 0.「AGC 024D」Isomorphism Freak 1.「APIO 2018」「洛谷 P4631」选圆圈 2.「UR #2」「UOJ #31」猪猪侠再战括号序列 3.「UR #3」「UO ...

  6. CDS标准视图:维护计划数据 C_MaintenancePlanDEX

    视图名称:维护计划数据 C_MaintenancePlanDEX 视图类型:基础 视图代码: 点击查看代码 @AbapCatalog.sqlViewName: 'CMAINTPLANDEX' @Aba ...

  7. 微服务实战系列(六)-网关springcloud zuul-copy

    1. 场景描述 今天接着介绍springcloud,今天介绍下springcloud的路由网关-Zuul,外围系统或者用户通过网关访问服务,网关通过注册中心找到对应提供服务的客户端,网关也需要到注册中 ...

  8. 【刷题】牛客模拟面试 > 模拟面试报告

    https://www.nowcoder.com/interview/ai/index 1-TCP协议的流量控制和拥塞控制 TCP的流量控制是基于窗口机制实现的: 在建立连接时, 发送方和接收方都会建 ...

  9. Golang-反射10

    http://c.biancheng.net/golang/reflect/ Go语言反射(reflection)简述 反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通过反 ...

  10. linux:用户管理

    用户账号添加.删除.修改以及用户密码的管理 用户组的管理 涉及三个文件: /etc/passwd    :存储用户的关键信息 /etc/group :存储用户组的关键信息 /etc/shadow :存 ...