热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

openGauss行存储索引机制

本文以BTree索引为例,介绍openGauss中行存储(

本文以B-Tree索引为例介绍openGauss中行存储(格式)表的索引机制。索引本质上是对数据的一种物理有序聚簇。有序聚簇参考的排序字段被称为索引键。为了节省存储空间,一般索引表中只存储有序聚簇的索引键键值以及对应元组在主表中的物理位置。在查询指定的索引键键值元组时,得益于有序聚簇排序,可以快速找到目标元组在主表中的物理位置,然后通过访问主表对应页面和偏移得到目标元组。B-Tree索引的组织结构如图所示。

  B-Tree索引页面间和页面内结构示意图

当前openGauss版本中,每个B-Tree的页面采用和行存储astore堆表页面基本相同的页面结构页面间按照树形结构组织,分为根节点页面、内部节点页面和叶子节点页面。其中,根节点页面和内部节点页面中的索引元组不直接指向堆表元组,而是指向下一层的内部节点页面或叶子节点页面;叶子节点页面位于B-Tree的最底层,叶子节点页面中的索引元组指向索引键值对应的堆表元组,即存储了该元组在堆表中的物理位置(堆表页面号和页内偏移)。

B-Tree索引元组结构由索引元组头NULL值字典和索引键值字段3分组成

索引元组头为IndexTupleData结构体定义代码如下所示。其中,t_tid堆表元组的位置或下一层索引页面的位置;t_info为标志位,记录键值中是否有NULL是否有变长键值索引访存方式信息以及元组长度

typedef struct IndexTupleData {
ItemPointerData t_tid; /* 堆表元组的物理行号 */
/* ---------------
* t_info标志位内容:
*
* 第15位:是否有NULL字段
* 第14位:是否有变长字段
* 第13位: 访存方式自定义
* 第0-12位: 元组长度
* ---------------
*/

unsigned short t_info; /* 如上 */
} IndexTupleData; /* 实际索引元组数据紧跟该结构体 */


astore堆表元组不同索引表的NULL值字典定长的一个bit位对应一个索引字段。当前最多支持32个索引字段因此该字典的长度为4个字节(如果要支持变长,那么长度加变长字典的实际空间并不会比定长的4个字节少多少)。如果索引元组头部t_info标志位中存在NULL值的bit位为0,那么该索引元组没有NULL值字典可以节约4个字节的空间。

索引键值字段和astore堆表元组的字段结构是完全相同的唯一区别是索引键值只保存创建索引的那些字段上的值

为了在一个索引页面中能够保存尽可能多的元组个数,降低整个B-Tree结构的层数索引元组和astore堆表元组的结构相比要紧凑很多去掉了一些和astore堆表元组冗余的结构体成员。在实际执行索引查询的时候,一般需要加载(索引层数+1)个物理页面才能找到目标元组。一般索引层数在24层之间因此减少一个层级近似就可以节省20%以上的元组访存开销

当前openGauss版本中索引元组头部不保存t_xmint_xmax这两个事务信息因此元组可见性的判断不会在遍历索引时确定而是要等到获得叶子索引最终指向的堆表元组以后通过结合查询快照和堆表元组的t_xmint_xmax信息才能判断对应堆表元组对本查询是否可见。将导致以下几个现象:

1) 对于删除的astore堆表元组其空间(至少其元组指针)不能立刻被释放,否则会留下悬空的索引指针,导致后续查询出现问题。

2) 对于被更新的astore堆表元组,如果更新前后索引字段的值发生变化,那么需要插入一条新的索引元组来指向更新后的堆表元组。然而即使更新前后所有索引字段的值没有发生变化考虑到可能还有并发的查询需要访问老元组因此老索引元组还要保留。同时要么插入一条新索引元组来指向更新后的堆表元组或者也可以通过将更新后元组的位置信息保存在老元组中,这样通过原来的一条索引元组,就可以一并查到更新前后的两条新、老元组了。但是这种场景下老堆表元组的清理又变得复杂起来,否则还会存在悬挂索引指针的问题。

为了解决上述这些问题openGauss当前提供了三种空间管理和回收的机制。在对astore堆表进行轻量级清理时无法清理索引中的垃圾数据。只有对astore进行中量级VACUUM清理或者重量级VACUUM FULL清理时才能够清理对应索引中的垃圾数据

最后上述索引可见性判断机制有一种例外场景如果查询不涉及非索引字段,如显示查询索引字段内容、或“SELECT COUNT(*)类查询,且索引字段t_tid指向的astore堆表页面对应的VMvisibility map可见性位图比特位为1,那么该索引元组被认为是可见的,这种扫描方式称为“Index Only Scan”。该扫描方式不仅提高了可见性判断的效率更重要的是避免了对于堆表页面的访问,从而可以节省大量I/O开销在页面空闲空间回收过程中,如果被清理的堆表页面上的所有元组对于当前所有正在执行的事务都可见,那么对应的VM比特位会被置为1;后续如果该堆表页面上有新的插入、删除或更新操作之后,都会将对应的VM比特位置为0

 

openGauss中的行存储索引表访存接口如所示

  行存储索引表访存接口

接口名称

接口含义

index_open

打开一个索引表,得到索引表的相关元信息

index_close

关闭一个索引表,释放该表的加锁或引用

index_beginscan

初始化索引扫描操作

index_beginscan_bitmap

初始化bitmap索引扫描操作

index_endscan

结束并释放索引扫描操作

index_rescan

重新开始索引扫描操作

index_markpos

记录当前索引扫描位置

index_restrpos

重置索引扫描位置

index_getnext

获取下一条符合索引条件的元组

index_getnext_tid

获取下一条符合索引条件的元组指针

index_fetch_heap

根据上面的指针,获取具体的堆表元组

index_getbitmap

获取符合索引条件的所有堆表元组指针组成的bitmap

index_bulk_delete

清理索引页面上的无效元组

index_vacuum_cleanup

索引页面清理之后的统计信息和空闲空间信息更新

index_build

扫描堆表数据,构造索引表数据

 

和堆表存储接口不同,由于openGauss支持多种索引结构B-TreehashGINgeneralized inverted index,通用倒排索引)),每种索引结构内部的页面间组织方式以及扫描方式都不太相同,因此在上述接口中,没有直接定义底层的页面和元组操作而是进一步调用了各个索引自己的访存方式。不同索引的底层访存接口,可以在pg_am系统表中查询得到


推荐阅读
  • 基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析
    基址获取与驱动开发:内核中提取ntoskrnl模块的基地址方法解析 ... [详细]
  • 本文详细解析了使用C++实现的键盘输入记录程序的源代码,该程序在Windows应用程序开发中具有很高的实用价值。键盘记录功能不仅在远程控制软件中广泛应用,还为开发者提供了强大的调试和监控工具。通过具体实例,本文深入探讨了C++键盘记录程序的设计与实现,适合需要相关技术的开发者参考。 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • 在当前的软件开发领域,Lua 作为一种轻量级脚本语言,在 .NET 生态系统中的应用逐渐受到关注。本文探讨了 Lua 在 .NET 环境下的集成方法及其面临的挑战,包括性能优化、互操作性和生态支持等方面。尽管存在一定的技术障碍,但通过不断的学习和实践,开发者能够克服这些困难,拓展 Lua 在 .NET 中的应用场景。 ... [详细]
  • Python全局解释器锁(GIL)机制详解
    在Python中,线程是操作系统级别的原生线程。为了确保多线程环境下的内存安全,Python虚拟机引入了全局解释器锁(Global Interpreter Lock,简称GIL)。GIL是一种互斥锁,用于保护对解释器状态的访问,防止多个线程同时执行字节码。尽管GIL有助于简化内存管理,但它也限制了多核处理器上多线程程序的并行性能。本文将深入探讨GIL的工作原理及其对Python多线程编程的影响。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 投融资周报 | Circle 达成 4 亿美元融资协议,唯一艺术平台 A 轮融资超千万美元 ... [详细]
  • 深入解析 Android TextView 中 getImeActionLabel() 方法的使用与代码示例 ... [详细]
  • 揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节
    揭秘腾讯云CynosDB计算层设计优化背后的不为人知的故事与技术细节 ... [详细]
  • Java集合框架特性详解与开发实践笔记
    Java集合框架特性详解与开发实践笔记 ... [详细]
  • 经过两天的努力,终于成功解决了半平面交模板题POJ3335的问题。原来是在`OnLeft`函数中漏掉了关键的等于号。通过这次训练,不仅加深了对半平面交算法的理解,还提升了调试和代码实现的能力。未来将继续深入研究计算几何的其他核心问题,进一步巩固和拓展相关知识。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
author-avatar
后果搞活棵_654_962
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有