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

openGauss现网案例(一) ----- 索引属性indcheckxmin=true

原创 丑丑的老太婆 2025-01-04
161

一、问题描述

      数据库已运行很长时间,某天客户运维人员突然查询某个普通表上的某个索引的属性indcheckxmin,发现该属性是true,但索引仍可以正常使用。咨询为什么该索引的indcheckxmin=true,而表上其他索引的indcheckxmin=false。

二、初步分析过程

   2.1 查询pg官网手册

indcheckxmin bool

If true, queries must not use the index until the xmin of this pg_index row is below their TransactionXmin event horizon, because the table may contain broken HOT chains with incompatible rows that they can see

    雪友们,见谅哈!毕竟不是英专生,只能通俗的翻译一下就是:如果indcheckxmin = true,则查询业务 在 "这个索引元组的xmin" 小于 "事务的阈值" 之前是不能使用该索引,因为这个表可能包含一个 "断热链",即我们可以看见一个不兼容的行数据。

    如果对数据库内核的研究功力不够深厚,可能无法一下子理解,不过接着往下看,必有 "朗" 的桃花源记feeling。

   2.2 先了解一下什么叫"断链"

        HOT链发生"断链" 的情况可能是两种:(1) 索引元组的键值更新    (2)当更新的元组存储在另一页中,和旧元组不存储在同一页。

        见博客《openGauss内核求索 ---- HOT链》

   2.3 代码分析

       @第一步@:搜索到indcheckxmin = true的地方,先锁定到index_build函数中,分析if条件语句,首先ii_BrokenHotChain 不确定,!reindex = true,不是ustore表,不是分区表,不是并发创建索引场景(非CIC场景)。因此推测应该是 ii_BrokenHotChain = true 导致。

     @第二步@:搜索ii_BrokenHotChain = true 的位置,找到IndexBuildHeapScan函数,发现在索引创建的时候,扫描heap表的时候,发现tuple是HEAPTUPLE_RECENTLY_DEAD情况。

       @第三步@:在上一步基础上往上搜索代码,发现 result = HEAPTUPLE_RECENTLY_DEAD 来源于HeapTupleSatisfiesVaccum的结果。

      @第四步@:查看HeapTupleSatisfiesVaccum函数,可知如果该tuple的修改已经提交,但xmax > OldestXmin,但返回HEAPTUPLE_RECENTLY_DEAD。

    @第五步@:通过回溯上面四步可知场景如下,用户在表t1上创建索引,在创建索引过程中需要扫描数据表,每一条扫描到的tuple都需要做HeapTupleSatisfiesVaccum判断,其中一条tuple的xmax已提交但小于OldestXmin,因此认为该tuple是HEAPTUPLE_RECENTLY_DEAD情况(第四步),即表明该tuple最近做过修改, 可能是update或者是delete,然后在对该HEAPTUPLE_RECENTLY_DEAD情况具体分析,发现该tuple的标志位是 HEAP_HOT_UPDATED ,所以这条tuple可能不久前刚被update过,设置ii_BrokenHotChain = true (第二步),到这里就很疑惑为什么是设置这个参数?是2.2章节里面提到的"hot断链"的场景吗?接下来有点绕脑子了。


三、深入理论分析

   3.1 场景1- 先create index后update索引键

        

          如上图所示,(1)先创建表t1;(2) 创建索引,索引的叶子节点指向行指针tp1,行指针tp1指向tuple1;(3) update更新索引键值,如果被更新的行tuple2与旧行数据tuple1存储在同一个页面,则不会插入相应的索引元组,并将老数据行的t_informask2字段设置为HEAP_HOT_UPDATE位, 新数据行的t_informask2字段设置为HEAP_ONLY_TUPLE位。那么就形成了图中的一条关系链1索引叶子节点索引元组 --> 行指针tp1 --> tuple1 --> tuple2,这就是使用HOT更新元组tuple1之后,使用索引扫描来访问更新后的元组的逻辑线,这个逻辑关系链先称之为"HOT链",是理想中的样子。

   3.2 场景2 - 先update索引键后create index

        

        如上图所示,(1)先创建表t1;(2) update更新索引键值,当前还没有创建索引,因此只涉及到数据页面,将老数据行的t_informask2字段设置为HEAP_HOT_UPDATE位, 新数据行的t_informask2字段设置为HEAP_ONLY_TUPLE位;(3)创建索引,根据章节二的代码分析可知,扫描tuple发现HEAP_HOT_UPDATE位,则该行不创建索引,即代码中indexIt=false。 而只更新hot链末位的tuple,如图中tuple2,因此逆向找到行指针tp2,索引创建叶子节点中的索引元组指向tp2,即关系链2为: 索引叶子节点索引元组 --> 行指针tp2 --> tuple2。@结论1@:所以当前的关系链2相较于场景1的关系链1,在tuple1还没有清理的情况下,可知tuple1-->tuple2这一关系断了,因此pg在这里称之为"BrokenHotChain ",是发生场景2后现实的样子。(理想是丰满的,现实是骨感的!)

   3.3 进一步猜想

     (还需要深入想一下,目前只是假设了一些情况,但是原因还不很充足)

       根据场景2,可知发生了章节2.1-查询pg官网手册中描述的"table may contain broken HOT chains";并且在场景2中,可能存在 一些事务(xid=8) 是在 第三步 update(xmin=9) 之前发生,假设TransactionXmin=7,因此tuple2应该对 这些事务(xid<9) 不可见的,而在场景2情况下,索引是直接创建在指向最新tuple2的tp2上,如果不做任何检查机制,索引扫描会直接根据tp2找到tuple2,即pg官网手册中描述的" broken HOT chains with incompatible rows that they can see" ,这些事务可能会看到不兼容的行(因为索引元组没有可见性判断,刚好修改的是索引键,为了减少I/O(输入/输出)成本,当SELECT语句的所有目标条目都包含在索引键中时,仅索引扫描(通常称为仅索引访问)直接使用索引键而不需要回表访问相应的表页。几乎所有的商业RDBMS都提供了这种技术,比如DB2和Oracle,PostgreSQL从9.2版开始引入了这个选项),因此根据可见性表的判断可能会直接返回当前索引元组的值,所以需要一些其他机制来检查可见性。另外一种是 第三步 update(xmin=6) ,假设TransactionXmin=7,那么查询事务此时都不应该使用该索引,没有机制可以检查不兼容的行的可见性。是因此设置indcheckxmin=true,来表明是场景2情况。

   3.4 从代码分析indcheckxmin的使用

        在仅索引扫描情况下,根据indcheckxmin=true,如果当前索引表元组的xmin > TransactionXmin,则表明发生了场景2,创建索引不久前刚做过update,所以需要通过CSN来检查可见性。即官网手册中描述的"queries must not use the index until the xmin of this pg_index row is below their TransactionXmin event horizon"。

@结论@

如果indcheckxmin = true

当这个索引元组的xmin <  事务的阈值时,查询业务不能使用索引,索引不可用

当这个索引元组的xmin >  事务的阈值时,查询业务能使用索引,但需要通过CSN检查可见性,索引可用

代码如下:

/*
             * If the index is valid, but cannot yet be used, ignore it; but
             * mark the plan we are generating as transient. See
             * src/backend/access/heap/README.HOT for discussion.
             */
            if (index->indcheckxmin) {
                TransactionId xmin = HeapTupleGetRawXmin(indexRelation->rd_indextuple);
                if (RelationIsUBTree(indexRelation) && TransactionIdIsCurrentTransactionId(xmin) &&
                    !IsolationUsesXactSnapshot()) {
                    /* new created ubtree index of the current Read Committed transaction is still valid */
                } else if (!TransactionIdPrecedes(xmin, u_sess->utils_cxt.TransactionXmin)) {
                    /*
                     * Since the TransactionXmin won't advance immediately(see CalculateLocalLatestSnapshot),
                     * we need to check CSN for the visibility.
                     */
                    CommitSeqNo csn = TransactionIdGetCommitSeqNo(xmin, false, true, false, NULL);
                    if (!COMMITSEQNO_IS_COMMITTED(csn) || csn >= u_sess->utils_cxt.CurrentSnapshot->snapshotcsn) {
                        root->glob->transientPlan = true;
                        index_close(indexRelation, NoLock);
                        continue;
                    }
                } 
复制


      

最后修改时间:2025-01-06 21:41:06
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
2人已赞赏
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

目录
  • 一、问题描述
  • 二、初步分析过程
    •    2.1 查询pg官网手册
    •    2.3 代码分析
  • 三、深入理论分析
    •    3.1 场景1- 先create index后update索引键
    •    3.2 场景2 - 先update索引键后create index
    •    3.3 进一步猜想
    •    3.4 从代码分析indcheckxmin的使用