暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

学了这么久,PostgreSQL 这些指标到底是什么?

数据库杂记 2024-09-18
258

1

什么是可见性指标?

简单来说,可见性指的是在特定的状态、后台进程或事务中,一行数据(默认为堆元组)是否应该向用户显示。

例如:
  • 用户通过UPDATE将数据记录从‘A’更改为‘B’。
  • PostgreSQL 通过INSERT新记录‘B’的方式处理,同时将记录‘A’标记为‘不可见’。
  • 系统中同时存在这两条记录,但‘B’是可见的,‘A’则不可见。
  • 已被删除或不可见的记录也被称为‘死’元组。
  • VACUUM进程的职责之一是清除这些‘死’元组以释放空间。

MVCC(多版本并发控制)作为一种方法,在每次写操作都会创建数据的“新版本”,同时保留“旧版本”。该方法允许并发的读写操作而不会出现相互阻塞。PostgreSQL使用MVCC的变体(也称快照隔离)来隔离并发事务。

因此,单个数据可能会有多个“版本”,PostgreSQL负责根据多种因素确定应向用户呈现哪个“版本”。这个过程也被称为“可见性检查”或“可见性控制”。

在本文中,我们将深入探讨PostgreSQL的可见性检查机制,以了解其工作原理。


2

可见性指标

据我所知,PostgreSQL 根据以下指标确定数据的可见性:
  • Transaction ID
  • xmin
  • xmax
  • cid
  • transaction snapshot
  • CLOG (Commit Log)
  • hintbit

这些指标到底是什么?让我们来一探究竟。


3

事务 ID

这应该是不言自明的。在 PostgreSQL 中,所有事务都与一个被称为事务ID的编号相关联。这主要用于检查相关数据是否在当前事务中插入或删除。我们将在文章的后面部分对此进行进一步探讨。

xmin, xmax, cid 和hintbit


这些指标被归为一类,因为它们都存储在每个堆数据元组的头部,并且每个数据元组可能具有不同的值,从而导致不同的可见性检查结果。下面的图像显示了它们存储在元组头部的位置:



xmin:
这是插入该堆数据元组的事务ID

xmax:
  • 这是删除该堆数据元组的事务ID。
  • 如果元组头部含有有效的、已提交的 xmax 值,通常说这个元组是“已删除的”或“不可见的”。

cid:
  • 这是在事务中插入此数据元组的命令ID。
  • 主要用于确定当前事务中的可见性。
  • 如果一个事务包含 SELECT 和其他 DML 查询(INSERT、UPDATE 和 DELETE),那么可以使用cid 来确定一个数据元组是在 SELECT 之前还是之后被插入或删除。

hintbit:
  • 这是一个标志字段,用于存储来自CLOG的查询结果,这样PostgreSQL就不必每次都查找CLOG。CLOG指的是提交日志,下文会详细解释。
  • 如果hintbit设置了HEAP_XMIN_COMMITTED标志,这意味着插入此元组的事务ID(即xmin)已提交。
  • 如果PostgreSQL可以直接从hintbit获取这些信息,那么它可以节省一些查找CLOG的时间,从而提高性能。
  • 可能的hintbit标志值如下:


4

CLOG (Commit Log)

CLOG 是存储在共享内存中并持久化在 $PGDATA/pg_xact 目录下的数据结构。CLOG 的目的是记录事务的提交状态。状态可以是以下之一:

  • COMMITTED
  • IN_PROGRESS
  • ABORTED
  • SUB_COMMITTED

例如:
如果一个元组的 xmin 是 103,而 CLOG 显示 103 是 ABORTED(如下图所示),这意味着 103 不可见,因为插入该数据元组的事务已经 ABORTED(即中止)。为了提高性能,PostgreSQL 将会标记 hintbit 为 HEAP_XMIN_INVALID,这样下次再访问同样的数据元组时就不需要再从 CLOG 中查找了。


5

事物快照

事务快照是另一个数据结构,用于存储某个时间点的活跃事务信息。它主要用来在并发事务中隔离数据元组。一个数据元组可能被另一个事务插入并提交了,但是我持有的事务快照可能是过去的。在这种情况下,根据我过去的快照,这个数据元组对我来说仍然是不可见的。

一个事务快照可以由几个事务号来表示:
    [xmin, xmax, xip_list]

    • xmin: 仍然活跃的最小事务ID — 小于这个值的事务已经提交或回滚。
    • xmax: 已提交或回滚的最大事务ID + 1 — 大于或等于这个值的事务尚未提交或回滚。
    • xip_list: 正在进行中的事务号列表 — 应该大于xmin并且小于xmax。

    请注意,事务快照中的 xmin 和 xmax 的定义与上文中存储在元组头部的 xmin 和 xmax 是不同的。

    通过示例可以更好的解释事务快照。

    假设我们有三个并发的后端进程 A、B 和 C,分别启动事务号为 747、748 和 749,每个事务插入了数据记录 'A'、'B' 和 'C'。
      postgres=# BEGIN;
      Postgres*# INSERT INTO mytable VALUES('A');
      Postgres*# SELECT txid_current();
      txid_current
      --------------
      747
      (1 row)

        postgres=# BEGIN;
        postgres=# INSERT INTO mytable VALUES('B');
        postgres=# SELECT txid_current();
        txid_current
        --------------
        748
        (1 row)

          postgres=# BEGIN;
          Postgres*# INSERT INTO mytable VALUES('C');
          Postgres*# SELECT txid_current();
          txid_current
          --------------
          749
          (1 row)

          然后,我们提交了事务 749,并通过 "pg_current_snapshot()" 获取了当前的事务快照。
            postgres=# BEGIN;
            Postgres*# INSERT INTO mytable VALUES('A');
            Postgres*# SELECT txid_current();
            txid_current
            --------------
            747
            (1 row)



              postgres=# BEGIN;
              postgres=# INSERT INTO mytable VALUES('B');
              postgres=# SELECT txid_current();
              txid_current
              --------------
              748
              (1 row)

                postgres=# BEGIN;
                Postgres*# INSERT INTO mytable VALUES('C');
                Postgres*# SELECT txid_current();
                txid_current
                --------------
                749
                (1 row)


                Postgres*# COMMIT;


                postgres=# select pg_current_snapshot();
                pg_current_snapshot
                ---------------------
                747:750:747,748
                (1 row)

                从后端进程 C 的角度来看,事务快照显示:
                • 747 是仍然活跃的最小事务ID。
                • 750 是已提交或已中止的最大事务ID + 1(749 + 1)。
                • 747 和 748 是仍在进行中的事务。

                这意味着:
                • C 能够看到由事务ID小于750的数据元组插入的数据,但不能看到事务ID为747和748的数据,因为它们仍在进行中(尚未提交或回滚)。
                • C 不能看到未来插入的数据元组(事务ID大于750的数据)。
                • C 能够看到由自己在事务749中插入的数据元组,因为它刚刚提交了这个事务。

                我们也可以在事务进行中查看 A 和 B 的事务快照。
                  postgres=# BEGIN;
                  Postgres*# INSERT INTO mytable VALUES('A');
                  Postgres*# SELECT txid_current();
                  txid_current
                  --------------
                  747
                  (1 row)






                  Postgres*# select pg_current_snapshot();
                  pg_current_snapshot
                  ---------------------
                  747:750:748
                  (1 row)

                    postgres=# BEGIN;
                    postgres=# INSERT INTO mytable VALUES('B');
                    postgres=# SELECT txid_current();
                    txid_current
                    --------------
                    748
                    (1 row)






                    postgres=*# select pg_current_snapshot();
                    pg_current_snapshot
                    ---------------------
                    747:750:747
                    (1 row)

                      postgres=# BEGIN;
                      Postgres*# INSERT INTO mytable VALUES('C');
                      Postgres*# SELECT txid_current();
                      txid_current
                      --------------
                      749
                      (1 row)


                      Postgres*# COMMIT;


                      postgres=# select pg_current_snapshot();
                      pg_current_snapshot
                      ---------------------
                      747:750:747,748
                      (1 row)

                      如您所见,A 的事务快照表明 B(748)正在进行中,因此它还不能看到 B(748)插入的数据元组。事务 747 是当前 A 所在的事务,但在快照中没有标记,因此它可以看到当前事务中由自己插入的数据。B 的事务快照表明情况恰恰相反。


                      6

                      确定可见性

                      上面提到的可见性指标只是部分指标;这意味着我们不能仅凭其中一个或少数几个指示来确定数据元组的真实可见性。必须考虑全部指标来计算正确的可见性。

                      让我们总结一下决定可见性的因素:
                      • xmin、xmax、cid 和 hintbit 存储在元组头部。
                      • 快照(snapshot)、事务ID在启动事务时由事务管理器提供。
                      • CLOG 存储在共享内存中

                      下图是可见性检查的简化说明:


                      这只是对整个可见性检查过程的简要概述。该过程可以在 src/backend/heap/heapam_visibility.c 文件的 heapTupleSatisfiesMVCC() 函数中找到。当然,实际的逻辑比上面的示意图更复杂。

                      我已经研究了 PostgreSQL 可见性检查逻辑的整个过程,并制作了下面更详细的流程图:可见性检查可以像这张图展示的那样深入,但由于使用了hintbit,只需通过使用数据元组的 xmin 和 xmax 值检查 hintbit 和快照,就可以很快完成大多数可见性检查。



                      7

                      总结

                      如果您是一名在从事分布式数据库、共享存储集群或分片技术的 PostgreSQL 开发者,我相信本文能为您提供有关 PostgreSQL 如何处理可见性检查的见解。当我们开始与多个数据库节点交互时,数据的原子性和可见性始终是一个问题。如何确保整个集群中的数据一致性是一个常见的挑战。了解事务和可见性的基本原理可以帮助我们解决这些问题。

                      关注公众号,了解更多社区动态


                      - 我们是谁 -

                      lvorySQL 是由瀚高基础软件股份有限公司主导研发的,一款基于 PostgreSQL 并深度兼容 Oracle 的开源数据库系统。IvorySQL 从底层代码层面深入把握开源技术的发展趋势,基于 PostgreSQL 16.3 的最新内核进行构建,同时提供了更加全面灵活的Oracle 兼容功能,具备高度的 SQL 和 PL/SQL 兼容性,能够满足企业对于数据库系统多样化和高兼容性的需求。

                      - 推荐阅读 -

                      【IvorySQL技术指南】

                      Centos7 被停用!如何利用 Ora2Pg 将 Oracle 迁移至 IvorySQL?

                      聚焦 PGConf.dev 2024 聊聊 IvorySQL 最新应用实践

                      WAL-G完美支持IvorySQL的备份恢复

                      IvorySQL在Neon平台上的迅速部署和灵活应用

                      IvorySQL Operator | 一键部署IvorySQL集群,高效管理与个性化配置尽在掌

                      pgvector扩展在IvorySQL Oracle兼容模式下的应用实践

                      IvorySQL 3.2 贡献独门秘籍,可以平滑迁移Oracle???

                      IvorySQL 3.2 :基于PG16.2,新增Oracle XML函数兼容功能

                      基于IvorySQL+Patroni+vip-manager构建高可用集群

                      备份恢复新体验!pgBackRest与IvorySQL的完美融合

                      最新版本!PG16.0最新内核,实现兼容Oracle数据库再升级

                      IvorySQL的IVY_GUC框架介绍与使用指南

                      IvorySQL新增命令及有关配置参数介绍

                      IvorySQL在「兼容oracle」方面做了哪些工作?

                      IvorySQL内核的Serverless数据库--HGNeon

                      迁移Oracle数据库至IvorySQL(PG兼容模式)

                      IvorySQL新增命令及有关配置参数介绍


                      【PG技术指南】
                      了解 PostgerSQL 的门户 – Executor vs Process Utility
                      数据库泰斗Stonebraker :“警惕数据库技术的过分炒作!”
                      2024 PGConf.dev 技术大盘点(下)
                      必看!!! 2024 最新 PG 硬核干货大盘点(上)
                      初学者指南 | PostgreSQL中的加密机制如何运作?
                      DBOS革命:迈克尔·斯通布雷克和无服务器计算的未来
                      7个维度!PostgreSQL生态展望:2024路向何方?
                      PostgreSQL16中的新增功能:双向逻辑复制
                      Kubernetes上PostgreSQL的存储策略
                      7个工具!让Oracle 迁移至 PostgreSQL成为可能!
                      4步上手Meson:让PostgreSQL 16 构建更现代化!
                      PostgreSQL 中的 JSON:彻底改变数据库中的数据灵活性




                      IvorySQL


                        一款开源的兼容Oracle的PostgreSQL


                      官方网址:
                      https://www.ivorysql.org
                      社区仓库:
                      https://github.com/IvorySQL/IvorySQL
                      IvorySQL社区欢迎并赞赏所有类型的贡献,期待您的加入!
                      记得在GitHub给我们一个 ⭐奥~


                      点击文末的 “阅读原文”,开启您的IvorySQL3.0 Release Notes探索之旅,深入了解更多独家特性。

                      >>>最新主版本体验链接:
                      https://github.com/IvorySQL/IvorySQL/releases

                      文章转载自数据库杂记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                      评论