postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

news2025/1/9 16:28:21

一、 难以理解的场景

postgresql源码学习(十九)—— MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客

       在前篇的可见性判断中有个一直没想明白的问题 —— 本事务插入的数据,什么场景可能会出现去查询获取快照后插入的数据这种情况?因为理论上事务内顺序的插入和查询,查的都是快照插入前的数据。今天终于发现了答案,愉快地记录下来~ 

经过好多文章和资料的查找,终于查到了一个案例 —— 游标,下面案例参考自

一文搞懂费解的cmin和cmax

       游标在声明时就获取一个快照,后续对于表的更改不影响到游标的取值,所以这也存在类似不同事务交替执行产生的数据可见性的问题。如下示例,Fetch游标时看到的是声明游标时的数据快照而不是Fetch执行时,即声明游标后对数据的变更对该游标不可见

create table cur_test(a int);
begin;
insert into cur_test values(1);
select xmin,xmax,cmin,cmax,a from cur_test;
declare mycursor cursor for select xmin,xmax,cmin,cmax,a from cur_test ;
insert into cur_test values(2);
fetch all from mycursor;
select xmin,xmax,cmin,cmax,a from cur_test;

 

二、 cid是什么

       在不同事务中,可以根据xmin和xmax判断事务可见性。那同一个事务内怎么办?就需要用到cid(command id),cid 表示在该事务中,执行当前dml前还执行过几条dml,因此cid越大,写入顺序越靠后。 再回顾一下它的定义:

postgresql源码学习(十六)—— MVCC①-元组上的版本信息_Hehuyi_In的博客-CSDN博客_pgsql 怎么查看元组中的txid

/* 
 * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac in three
 * physical fields.  Xmin and Xmax are always really stored, but Cmin, Cmax
 * and Xvac share a field.  This works because we know that Cmin and Cmax
 * are only interesting for the lifetime of the inserting and deleting
 * transaction respectively.  If a tuple is inserted and deleted in the same
 * transaction, we store a "combo" command id that can be mapped to the real
 * cmin and cmax, but only by use of local state within the originating
 * backend.  See combocid.c for more details.  Meanwhile, Xvac is only set by
 * old-style VACUUM FULL, which does not have any command sub-structure and so
 * does not need either Cmin or Cmax.  (This requires that old-style VACUUM
 * FULL never try to move a tuple whose Cmin or Cmax is still interesting,
 * ie, an insert-in-progress or delete-in-progress tuple.)
 */

typedef struct HeapTupleFields
{
    TransactionId t_xmin;       /* inserting xact ID */
    TransactionId t_xmax;       /* deleting or locking xact ID */

    union
    {
        CommandId   t_cid;      /* inserting or deleting command ID, or both */
        TransactionId t_xvac;   /* old-style VACUUM FULL xact ID */
    }           t_field3;
} HeapTupleFields;

可以看到,虽然在前面的select中它是cmin,cmax两个值,但在源代码中,就是一个t_cid。

主要特点如下:

  • cmin和cmax则分别代表插入、删除操作
  • 8.4以前,cmincmax分开存储,后来考虑到对同一事务内的同一行既执行插入又执行删除情况不多见,所以就合并成了一个字段,节省资源
  • 如果真的出现同一事务中对同一行既执行插入、又执行删除,则通过combo cid 查找真正的cmin和cmax
  • cmincmax始终是一致的,因为本质上都是 t_cid 字段
  • 由于vacuum full不关心cmincmax,可以合并存储两者,进一步节省资源
  • CommandIduint32类型,最大支持2^32 - 1个命令,为了节省资源,查询不会增加cid

三、 获取cmin与cmax

1. HeapTupleHeaderGetCmin和HeapTupleHeaderGetCmax

      在HeapTupleSatisfiesMVCC函数中,获取元组cmin与cmax相关的主要是HeapTupleHeaderGetCmin和HeapTupleHeaderGetCmax两个函数。

/*
 * GetCmin and GetCmax assert that they are only called in situations where
 * they make sense, that is, can deliver a useful answer.  If you have
 * reason to examine a tuple's t_cid field from a transaction other than
 * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
 */

CommandId
HeapTupleHeaderGetCmin(HeapTupleHeader tup)
{
    CommandId   cid = HeapTupleHeaderGetRawCommandId(tup);

    Assert(!(tup->t_infomask & HEAP_MOVED));
    Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));

    if (tup->t_infomask & HEAP_COMBOCID)
        return GetRealCmin(cid);
    else
        return cid;
}
CommandId
HeapTupleHeaderGetCmax(HeapTupleHeader tup)
{
    CommandId   cid = HeapTupleHeaderGetRawCommandId(tup);

    Assert(!(tup->t_infomask & HEAP_MOVED));

    /*
     * Because GetUpdateXid() performs memory allocations if xmax is a
     * multixact we can't Assert() if we're inside a critical section. This
     * weakens the check, but not using GetCmax() inside one would complicate
     * things too much.
     */
    Assert(CritSectionCount > 0 ||
           TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));

    if (tup->t_infomask & HEAP_COMBOCID)
        return GetRealCmax(cid);
    else
        return cid;
}

可以看到,它们很类似:

  • 对于非COMBOCID,核心函数都是 HeapTupleHeaderGetRawCommandId
  • COMBOCID使用的是标记位t_infomask判断
  • COMBOCID情况下分别使用GetRealCmin和GetRealCmax
  • 无论哪个函数,最后返回的都是cid一个字段,因此cmincmax始终是一致的

2. HeapTupleHeaderGetRawCommandId函数

可以看到,其实就是去获取元组的t_cid

https://img-blog.csdnimg.cn/2c622678070f4fbbb0d37fa3cec1cda4.png

/*
 * HeapTupleHeaderGetRawCommandId will give you what's in the header whether
 * it is useful or not.  Most code should use HeapTupleHeaderGetCmin or
 * HeapTupleHeaderGetCmax instead, but note that those Assert that you can
 * get a legitimate(合法的) result, ie you are in the originating transaction!
 */
#define HeapTupleHeaderGetRawCommandId(tup) \
( \
    (tup)->t_choice.t_heap.t_field3.t_cid \
)

四、 combo cid

1. HEAP_COMBOCID标记位

从前面代码可以看到,COMBOCID使用的是标记位t_infomask判断,我们来验证一下。

t_infomask值为32,转成16进制就是0x0020,也就是HEAP_COMBOCID

2. 相关结构体

以下在combocid.c 文件中

/* Hash table to lookup combo CIDs by cmin and cmax,可以看到是通过哈希表去定位cmin,cmax的 */
static HTAB *comboHash = NULL;

/* Key and entry structures for the hash table,哈希表的key */
typedef struct
{
    CommandId   cmin;
    CommandId   cmax;
} ComboCidKeyData;

typedef ComboCidKeyData *ComboCidKey;

/* 哈希表的value */
typedef struct
{
    ComboCidKeyData key;
    CommandId   combocid;
} ComboCidEntryData;

typedef ComboCidEntryData *ComboCidEntry;

3. GetRealCmin和GetRealCmax函数

这两个函数相当简单,问题是过于简单好像看不出来什么东西...

static CommandId
GetRealCmin(CommandId combocid)
{
    Assert(combocid < usedComboCids);
    return comboCids[combocid].cmin;
}

static CommandId
GetRealCmax(CommandId combocid)
{
    Assert(combocid < usedComboCids);
    return comboCids[combocid].cmax;
}

其中的usedComboCids和comboCids数组都来自GetComboCommandId函数

4. GetComboCommandId函数

       从函数注释和调用栈都可以看到,它不是显式调用的,是一个内部函数,这里就有前面提到过的哈希表。

 

/**** Internal routines 内部流程 ****/

/*
 * Get a combo command id that maps to cmin and cmax.
 * We try to reuse old combo command ids when possible.
 */
static CommandId
GetComboCommandId(CommandId cmin, CommandId cmax)
{
    CommandId   combocid;
    ComboCidKeyData key;   // 哈希表key
    ComboCidEntry entry;   // 哈希表value
    bool        found;

    /*
     * Create the hash table and array the first time we need to use combo cids in the transaction.
      * 第一次使用时哈希表不存在,需要先创建
     */
    if (comboHash == NULL)
    {
        HASHCTL     hash_ctl;

        /* Make array first; existence of hash table asserts array exists
可以看到comboCids是ComboCidKeyData类型的数组(哈希表key数组) */
        comboCids = (ComboCidKeyData *)
            MemoryContextAlloc(TopTransactionContext,
                               sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE);
        sizeComboCids = CCID_ARRAY_SIZE;
        usedComboCids = 0;

        hash_ctl.keysize = sizeof(ComboCidKeyData);
        hash_ctl.entrysize = sizeof(ComboCidEntryData);
        hash_ctl.hcxt = TopTransactionContext;

/* 创建哈希表 */
        comboHash = hash_create("Combo CIDs",
                                CCID_HASH_SIZE,
                                &hash_ctl,
                                HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
    }

    /*
     * 超过长度则自动扩展数组
     */
    if (usedComboCids >= sizeComboCids)
    {
        int         newsize = sizeComboCids * 2;

        comboCids = (ComboCidKeyData *)
            repalloc(comboCids, sizeof(ComboCidKeyData) * newsize);
        sizeComboCids = newsize;
    }

    /* Lookup or create a hash entry with the desired cmin/cmax,根据key值从哈希表中查找cmin/cmax */

    /* We assume there is no struct padding in ComboCidKeyData! */
    key.cmin = cmin;
    key.cmax = cmax;
    entry = (ComboCidEntry) hash_search(comboHash,
                                        (void *) &key,
                                        HASH_ENTER,
                                        &found);

    if (found)
    {
        /* Reuse an existing combo CID,找到则返回combocid */
        return entry->combocid;
    }

    /* We have to create a new combo CID; we already made room in the array
否则需要新建一个combocid并以当前传入的cmin/cmax作为值,同时将计数器usedComboCids加一 */
    combocid = usedComboCids;

    comboCids[combocid].cmin = cmin;
    comboCids[combocid].cmax = cmax;
    usedComboCids++;

    /* 保存到哈希表的value中并返回 */
    entry->combocid = combocid;

    return combocid;
}

        因此,前面的GetRealCmin和GetRealCmax函数,只需要一步return comboCids[combocid].cmin和 return comboCids[combocid].cmax 即可从哈希表获取所需值。

参考

一文搞懂费解的cmin和cmax

postgresql源码学习(十六)—— MVCC①-元组上的版本信息_Hehuyi_In的博客-CSDN博客_pgsql 怎么查看元组中的txid

pg事务篇(三)—— 事务状态与Hint Bits(t_infomask)_Hehuyi_In的博客-CSDN博客

postgresql源码学习(十九)—— MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/21712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

路面坑洼检测中的视觉算法

3D道路成像和路面坑洼检测的经典工作综述。论文链接&#xff1a;https://arxiv.org/pdf/2204.13590.pdf 计算机视觉算法在3D道路成像和路面坑洼检测中的应用已有二十多年的历史。这里先介绍了用于2D和3D道路数据采集的传感系统&#xff0c;包括摄像机、激光扫描仪和微软Kinect…

汉兰达汽车发动机怠速抖动故障诊断方案设计

目录 一、课题简介 1 1.1课题基本内容 1 1.2课题解决的主要问题 1 1.3课题设计思路 1 二、毕业设计成果 2 2.1汉兰达汽车发动机怠速抖动故障现象描述 2 2.2 汉兰达汽车发动机怠速抖动故障原因分析 2 2.3汉兰达汽车发动机怠速抖动故障诊断与排除 6 2.4维修结论与建议 12 三、毕业…

java sleep yield join区别

1、sleep&#xff1a;让出CPU调度&#xff0c;Thread类的方法&#xff0c;必须带一个时间参数。会让当前线程休眠进入阻塞状态并释放CPU&#xff08;阿里面试题 Sleep释放CPU&#xff0c;wait 也会释放cpu&#xff0c;因为cpu资源太宝贵了&#xff0c;只有在线程running的时候&…

高效正则匹配工具

很多人都用过正则&#xff0c;但文章或许会给你一种全新的认识(思考) 以下内容适合高效率正则匹配&#xff08;比较适合正则匹配场景较多的情况&#xff09; 效率提升精华&#xff1a;本地缓存减少编译次数&#xff08;对effective java的思考&#xff0c;以及对数据库连接中…

Java中的装包(装箱)和拆包(装包)

装箱和拆箱 在Java的学习中&#xff0c;我们有的时候会设计装箱和拆箱的概念&#xff08;也就是常说的装包和拆包&#xff09;&#xff0c;这篇博客将详细讲解一下装箱和拆箱的概念及其用途。 装箱&#xff08;装包&#xff09;&#xff1a;将基本数据类型转换成包装类类型 拆…

websocket给指定客户端推送消息

业务场景 最近有一个业务场景是要做实时语音转义&#xff0c;考虑到实时性&#xff0c;所以决定采用websocket实现。 业务场景是A客户端(手机)进行语音转义的结果实时同步到B客户端(pc)&#xff0c;这就需要用到websocket将A转义的结果发送给服务端&#xff0c;服务端接收到A…

软件工程经济学复习题答案

1、利润 收入-成本费用 2、资产 流动资产非流动资产 3、显性成本可以用货币计量&#xff0c;是可以在会计的帐目上反映出来的 4、领取什么保险应缴纳个人所得税 商业保险 某企业一项固定资产的原价为8000 000元&#xff0c;预计使用年限为6年&#xff0c;预计净残值为5 0…

[LeetCode周赛复盘] 第 320 场周赛20221120

[LeetCode周赛复盘] 第 320 场周赛20221120 一、本周周赛总结二、 [Easy] 6241. 数组中不等三元组的数目1. 题目描述2. 思路分析3. 代码实现三、[Medium] 6242. 二叉搜索树最近节点查询1. 题目描述2. 思路分析3. 代码实现四、[Hard] 6243. 到达首都的最少油耗1. 题目描述2. 思路…

10_libpcap以及libnet

知识点1【飞秋欺骗】 1、windwos安装飞秋 双击运行 2、ubuntu安装飞秋 sudo apt-get install iptux ubuntu运行飞秋&#xff1a;iptux& 3、飞秋的格式&#xff1a; 版本:包编号:用户名:主机名:命令字:附加消息 飞秋的端口是2425固定的 1表示上线 32表示普通消息 1_i…

(经典dp) hdu 递推求解专题练习

文章目录前言题单hdu2044 一只小蜜蜂...hdu2045 不容易系列之(3)—— LELE的RPG难题hdu2046 骨牌铺方格hdu2047 阿牛的EOF牛肉串hdu2048 神、上帝以及老天爷hdu2049 不容易系列之(4)——考新郎hdu2050 折线分割平面END前言 题单&#xff1a;递推求解专题练习&#xff08;For Be…

华为机试 - 找出经过特定点的路径长度

目录 题目描述 输入描述 输出描述 用例 题目解析 算法源码 题目描述 无 输入描述 输入一个字符串&#xff0c;都是以大写字母组成&#xff0c;每个相邻的距离是 1&#xff0c; 第二行输入一个字符串&#xff0c;表示必过的点。 说明每个点可过多次。 输出描述 经过这…

精益(Lean)与ERP实施

周四、五看完了24小时不停的Lean Global Connection&#xff0c;总觉得要说些什么。 印象最深的有三个地方&#xff1a; 一是John Shook的话&#xff0c;他说Lean是一种Mindset。 这种Mindset是&#xff1a; 一种积极的态度&#xff0c;Problems solving, 把问题和挑战当成是…

Web 性能优化:TLS

个人博客 Web 性能优化&#xff1a;TCP&#x1f3af; Web 性能优化&#xff1a;TLSWeb 性能优化&#xff1a;HTTP “do it, do it work, do it better … and secure ☠️” 随着追逐利益而来的恶意参与者越来越多&#xff0c;当前的 Web 应用&#xff0c;已经从野蛮生长转而…

【通关MySQL】Java的JDBC编程

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【通关MySQL】 ✈️✈️本篇内容:Java的JDBC编程。 &#x1f680;&#x1f680;代码存放仓库gitee&#xff1a;MySQL码云存放&#xff01; ⛵⛵作者简介&#xff…

[附源码]java毕业设计-室内田径馆预约管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

轻量应用服务器和云服务器CVM有什么区别?

腾讯云新推出的轻量应用服务器Lighthouse和原来的CVM云服务器有什么区别&#xff1f;轻量应用服务器Lighthouse是一种易于使用和管理、适合承载轻量级业务负载的云服务器&#xff0c;主要用于Web网站应用&#xff0c;轻量服务器使用及后期运维更加简单方便&#xff1b;云服务器…

RNA-seq 保姆教程:差异表达分析(二)

介绍 RNA-seq 目前是测量细胞反应的最突出的方法之一。RNA-seq 不仅能够分析样本之间基因表达的差异&#xff0c;还可以发现新的亚型并分析 SNP 变异。本教程[1]将涵盖处理和分析差异基因表达数据的基本工作流程&#xff0c;旨在提供设置环境和运行比对工具的通用方法。由于完整…

计算机网络——数据链路层

数据链路层概述 数据链路层在网络体系结构中所处的地位 主机H1给主机H2发送数据时&#xff0c;需要通过路由器R1通过广域网链路转发到路由器R2&#xff0c;R2转发到主机H2。路由器转发只用到网络层及以下各层。【以上涉及数据包按网络体系结果逐层封装解封】 为了简单起见&am…

DevC++的调试方法

目录 Dev C调试程序 Dev C调试注意事项对于修改后的程序&#xff0c;调试程序之前一定要先编译程序。 要想学会编程,第一步就是要学会调试(想我这种码龄一年的人还不会调试,丢死人). 今天,为了让你们的脸丢少点,特意写了这篇博文,给予需要帮助的人. 所谓调试程序&#xff0…

[附源码]计算机毕业设计JAVA基于JSP健身房管理系统

[附源码]计算机毕业设计JAVA基于JSP健身房管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM m…