【PostgreSQL】【存储管理】表和元组的组织方式

  • 外存管理负责处理数据库与外存介质(PostgreSQL8.4.1版本中只支持磁盘的管理操作)的交互过程。
  • 在PostgreSQL中,外存管理由SMGR(主要代码在smgr.c中)提供了对外存的统一接口。
  • SMGR负责统管各种介质管理器,会根据上层的请求选择一个具体的介质管理器进行操作。
  • 每个表在磁盘中都以一定的结构进行存储,针对磁盘,外存管理模块提供了磁盘管理器和VFD机制。
  • 在PostgreSQL8.4.1版本中,还为每个表文件创建了两个附属文件,即空闲空间映射表(FSM)和可见性文件映射表(VM)。
  • 另外,对于大数据存储,PostgreSQL也提供了两种处理机制。

在这里插入图片描述

表和元组的组织方式

  • PostgreSQL中一个表中的元组按照创建顺序依次插入到表文件中。
  • 在进行VACUUM操作清除被删除的元组后,元组也可以以无序的方式插入到具有空间空间的文件块中
  • 元组之间不进行关联,这样的表文件称为堆文件。
  • PostgreSQL系统中包含了四种堆文件:
    • 普通堆:堆文件就是普通堆
    • 临时堆:临时堆和普通堆结构相同,但是临时堆仅在会话过程中临时创建,会话结束会自动结束。
    • 序列:一种特殊的单行表,它是一种元组值自动递增的特殊堆。
    • TOAST表:它其实也是一种普通堆,但是它被专门用于存储变长数据。
  • 尽管这几种堆的功能各异,但在底层的文件结构却是相似:每个堆文件由多个文件块组成。

文件块在物理磁盘中的存储形式:

在这里插入图片描述

PageHeaderData: 24字节长。包含关于页面的一般信息,包括空闲空间指针。

  • 结构体:
typedef struct PageHeaderData
{/* XXX LSN is member of *any* block, not only page-organized ones */PageXLogRecPtr pd_lsn;    /* LSN: next byte after last byte of xlog* record for last change to this page */uint16    pd_checksum;  /* checksum */uint16    pd_flags;    /* flag bits, see below */LocationIndex pd_lower;    /* offset to start of free space */LocationIndex pd_upper;    /* offset to end of free space */LocationIndex pd_special;  /* offset to start of special space */uint16    pd_pagesize_version;TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */ItemIdData  pd_linp[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */
} PageHeaderData;
类型长度描述
pd_lsnPageXLogRecPtr8 bytesLSN: 最后修改这个页面的WAL记录最后一个字节后面的第一个字节
pd_checksumuint162 bytes页面校验码
pd_flagsuint162 bytes标志位
pd_lowerLocationIndex2 bytes到空闲空间开头的偏移量
pd_upperLocationIndex2 bytes到空闲空间结尾的偏移量
pd_specialLocationIndex2 bytes到特殊空间开头的偏移量
pd_pagesize_versionuint162 bytes页面大小和布局版本号信息
pd_prune_xidTransactionId4 bytes页面上最老未删除XID,如果没有则为0

ItemIdData:

  • 结构体:src/include/storage/bufpage.h
typedef struct ItemIdData
{unsigned  lp_off:15,    /* offset to tuple (from start of page) */lp_flags:2,    /* state of line pointer, see below */lp_len:15;    /* byte length of tuple */
} ItemIdData;
  • 每个ItemIdData结构用来指向文件块中的元组,其中lp_off是元组在文件块中的偏移量,而lp_len则说明了该元组的长度,lp_flags表示元组的状态(分为未使用,正常使用,HOT重定向和死亡四种状态)
/** lp_flags has these possible states.  An UNUSED line pointer is available* for immediate re-use, the other states are not.*/
#define LP_UNUSED    0    /* unused (should always have lp_len=0) */
#define LP_NORMAL    1    /* used (should always have lp_len>0) */
#define LP_REDIRECT    2    /* HOT redirect (should have lp_len=0) */
#define LP_DEAD      3    /* dead, may or may not have storage */
  • 在页头后面是项标识符(ItemIdData),每个占用四个字节。
  • 一个项标识符包含一个到项开头的字节偏移量(它的长度以字节计), 以及一些属性位,这些属性位影响对它的解释。
  • 新的项标识符根据需要从未分配空间的开头分配。
  • 项标识符的数目可以通过查看pd_lower来判断,在分配新标识符的时候pd_lower会增长。
  • 因为一个项标识符在被释放前绝对不会移动,所以它的索引可以用于长期地引用一个项, 即使该项本身因为压缩空闲空间在页面内部进行了移动。
  • 实际上,PostgreSQL创建的每个指向项的指针(ItemPointer,也叫做CTID)都由一个页号和一个项标识符的索引组成。
  • 项本身存储在从未分配空间末尾开始从后向前分配的空间里。它们的实际结构取决于表包含的内容。表和序列都使用一种叫做 HeapTupleHeaderData的结构,

Freespace: 是指未分配的空间(空闲空间)

  • 新插入页面中的元组即对应的项标识符都将从这部分空间中来分配,其中Linp元素从Freespace的开头开始分配,而新元组数据则从尾部开始分配。

Special space:是特殊空间

  • 用于存放与索引方法相关的特定数据,不同的索引方法在Special space中存放不同的数据,比如,b-tree 索引用它存储指向页面的左右兄妹的链接,以及其他一些和索引结构相关的数据。
  • 由于索引文件的文件块和普通表文件的相同,因此Special space在普通表文件块中并没有使用,其内容被置为空。

Tuple:每个元组分两个部分元组头部和数据:元组头部存放该元组头部信息,数据部分存放用户存储的实际数据

  • 结构体:位于src/include/access/htup_details.h
struct HeapTupleHeaderData
{union{HeapTupleFields t_heap;DatumTupleFields t_datum;} t_choice;ItemPointerData t_ctid;    /* current TID of this or newer tuple (or a* speculative insertion token) *//* Fields below here must match MinimalTupleData! */
#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK2 2uint16    t_infomask2;  /* number of attributes + various flags */
#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK 3uint16    t_infomask;    /* various flag bits, see below */
#define FIELDNO_HEAPTUPLEHEADERDATA_HOFF 4uint8    t_hoff;      /* sizeof header incl. bitmap, padding *//* ^ - 23 bytes - ^ */
#define FIELDNO_HEAPTUPLEHEADERDATA_BITS 5bits8    t_bits[FLEXIBLE_ARRAY_MEMBER];  /* bitmap of NULLs *//* MORE DATA FOLLOWS AT END OF STRUCT */
};
  • 出于编程的考虑,PostgreSQL的源代码中常用指向HeapTupleHeaderData的结构指针HeapTupleHeader来访问元组的头部信息。
  • t_choice是具有两个成员的联合类型:
    • t_heap:
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;
    用于记录对元组执行插入/删除操作的事务ID和命令ID,这些信息主要用于并发控制时检查元组对事务的可见性。
- t_datum:
typedef struct DatumTupleFields
{int32    datum_len_;    /* varlena header (do not touch directly!) */int32    datum_typmod;  /* -1, or identifier of a record type */Oid      datum_typeid;  /* composite type OID, or RECORDOID *//** datum_typeid cannot be a domain over composite, only plain composite,* even if the datum is meant as a value of a domain-over-composite type.* This is in line with the general principle that CoerceToDomain does not* change the physical representation of the base type value.** Note: field ordering is chosen with thought that Oid might someday* widen to 64 bits.*/
} DatumTupleFields;
    当一个新元组在内存中形成时时候,我们并不关心其事务可见性,因此在t_choice中只需要用DatumTupleFields结构来记录元组的长度等信息。但是该元组插入到表文件时,需要在元组头信息中记录插该元组的事务和命令ID。故此时会把t_choice所占用的内存转化为HeapTupleFields结构,并填充相应数据后再进行元组的插入。
  • t_ctid:用于记录当前元组或者新元组的物理位置(块内偏移量和元组长度)

    如果元组更新,PostgreSQL对元组的更新采用的是标记删除旧版本并插入新版本的元组的方式,则记录的是新版本元组的物理位置。

  • t_infomask2:

    • 使用其低11位表示当前元组的属性个数,其他位则用于包括用于HOT技术及元组可见性的标记为。
  • t_infomask:

    • 用于标识元组当前的状态,比如元组是否具有OID,是否有空属性等,t_infomask的每一位对应不同的状态,共16种状态。
/** information stored in t_infomask:*/
#define HEAP_HASNULL      0x0001  /* has null attribute(s) */
#define HEAP_HASVARWIDTH    0x0002  /* has variable-width attribute(s) */
#define HEAP_HASEXTERNAL    0x0004  /* has external stored attribute(s) */
#define HEAP_HASOID_OLD      0x0008  /* has an object-id field */
#define HEAP_XMAX_KEYSHR_LOCK  0x0010  /* xmax is a key-shared locker */
#define HEAP_COMBOCID      0x0020  /* t_cid is a combo CID */
#define HEAP_XMAX_EXCL_LOCK    0x0040  /* xmax is exclusive locker */
#define HEAP_XMAX_LOCK_ONLY    0x0080  /* xmax, if valid, is only a locker */
#define HEAP_XMIN_COMMITTED    0x0100  /* t_xmin committed */
#define HEAP_XMIN_INVALID    0x0200  /* t_xmin invalid/aborted */
#define HEAP_XMAX_COMMITTED    0x0400  /* t_xmax committed */
#define HEAP_XMAX_INVALID    0x0800  /* t_xmax invalid/aborted */
#define HEAP_XMAX_IS_MULTI    0x1000  /* t_xmax is a MultiXactId */
#define HEAP_UPDATED      0x2000  /* this is UPDATEd version of row */
#define HEAP_MOVED_OFF      0x4000  /* moved to another place by pre-9.0* VACUUM FULL; kept for binary* upgrade support */
#define HEAP_MOVED_IN      0x8000  /* moved from another place by pre-9.0* VACUUM FULL; kept for binary* upgrade support */
  • t_hoff:
    • 表示该元组头的大小,到用户数据的偏移量
  • _bits[] : 数组表示该元组中那些字段为空

HOT技术:

  • PostgreSQL中对于元组采用多版本控制技术存储。
  • 对于元组的更新操作都会产生一个新版本,版本之间从老到新形成一条版本链,将旧版本的t_ctid字段指向下一个版本的位置即可。
  • 此外,更新操作不但会在表文件中产生元组的新版本,在表的每个索引中也会产生新版本的索引记录,即对一条元组的每个版本都有对应的索引记录。
  • 即使,更新操作没有修改索引属性,也会在每个索引中产生一个新版本。
  • 这技术的问题是浪费存储空间,旧版本占用的空间中有在进行VACUUM时才能被回收,增加了数据库的负担。
  • 为了解决这个问题,从版本8.3开始,使用一种HOT机制,当更新的元组同时满足如下条件时(通过HeapSatisfiesHOTUpdate函数判断)称为HOT元组
    • 所有索引技术都没有被修改过,索引键是否修改过是在执行时逐行判断的,因此若一条update语句修改了某属性,但是前后值相同则认为没有修改过。
    • 更新的元组新版本与旧版本在同一个文件块内,限制在同一个文件块的目的是为了通过版本链向后查找时不产生额外的I/O操作从而影响到性能。
  • HOT元组会被打上HEAP_ONLY_TUPLE标志,而HOT元组上的上一个版本则被打上HEAP_HOT_UODATE标志。
  • 更新一条HOT元组将不会在索引中引入新版本,当通过索引获取元组时首先会找到同一块中最老的版本,然后顺着版本链向后找,直到遇到HOT元组为止。
  • 因此HOT技术消除了拥有完全相同的键值索引记录,减少了索引大小。

在堆中删除一个元组的方法:理论上有两种方法

  • 直接物理删除:找到该元组所在的块,并将其读取到缓冲区,然后再缓冲区中删除这个元组,最后再将缓冲区的数据写回磁盘。
  • 标记删除:为每个元组使用额外的数据位作为删除标记。当删除元组时,只设置相应的删除标记,即可实现快速删除。这种方法并不会立即回收删除元组占用的空间。

PostgreSQL采用的是第二种方法,每个元组的头部信息就包含了这个删除标记,其中记录了删除这个元组的事务ID和命令ID。如果上述两个ID有效,则表明该元组被删除,若无效,说明该元组是有效的或者说没有被删除。这种方法对于多版本并发控制也是有好处的。

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

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

相关文章

动态链接那些事

1、为什么要动态链接 1.1 空间浪费 对于静态链接来说,在程序运行之前,会将程序所需的所有模块编译、链接成一个可执行文件。这种情况下,如果 Program1 和 Program2 都需要用到 Lib.o 模块,那么,内存中和磁盘中实际上就…

Guava限流器原理浅析

文章目录 基本知识限流器的类图使用示例 原理解析限流整体流程问题驱动1、限流器创建的时候会初始化令牌吗?2、令牌是如何放到桶里的?3、如果要获取的令牌数大于桶里的令牌数会怎么样4、令牌数量的更新会有并发问题吗 总结 实际工作中难免有限流的场景。…

外卖订餐系统:数字时代的美食点餐新体验

在数字时代,外卖订餐系统已经成为现代生活的一部分。它不仅改变了我们点餐的方式,还为餐饮业带来了巨大的变革。本文将深入探讨外卖订餐系统的崭新世界,探讨它的发展历程、优势和未来趋势。 从电话点餐到外卖订餐系统 许多人还记得过去打电…

Linux环境下gdb调试方法与演示

个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【Linux专栏】🎈 本专栏旨在分享学习Linux的一点学习心得,欢迎大家在评论区讨论💌 演示环境&#xff1…

初识jdbc

java中的数据存储技术 在Java中,数据库存取技术可分为如下几类: JDBC直接访问数据库 JDO (Java Data Object )技术 第三方O/R工具,如Hibernate, Mybatis 等 JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封…

华为云云耀云服务器L实例评测使用 | 通过程序实现直播流自动分段录制

华为云云耀云服务器L实例评测使用 | 通过程序实现直播流自动分段录制 1. 准备工作2. 环境搭建3. 心得总结 1. 准备工作 随着云计算时代的进一步深入,越来越多的中小企业企业与开发者需要一款简单易用、高能高效的云计算基础设施产品来支撑自身业务运营和创新开发。基…

基本的五大排序算法

目录: 一,直接插入算法 二,希尔排序算法 三,选择排序 四,堆排序 五,冒泡排序算法 简介: 排序算法目前是我们最常用的算法之一,据研究表明,目前排序占用计算机CPU的时…

TouchGFX之后端通信

在大多数应用中,UI需以某种方式连接到系统的其余部分,并发送和接收数据。 它可能会与硬件外设(传感器数据、模数转换和串行通信等)或其他软件模块进行交互通讯。 Model类​ 所有TouchGFX应用都有Model类,Model类除了存…

小白自己​制作一个苹果.ios安卓.apk文件app应用手机下载的代码合并文件一码双端的落地页面详细教程

小白自己制作一个苹果.ios安卓.apk文件app应用手机下载的代码落地页面详细教程 图片取自这里哈 我们在这篇文章中教你如何制作一个手机下载引导落地页。这个落地页将可以自动识别访问者使用的是安卓还是苹果设备,并引导下载相应的应用程序。让我们按照以下步骤一…

Python中aiohttp和aiofiles模块的安装

Python中aiohttp和aiofiles模块的安装 前言 在进行asyncio多任务爬取的时候,配合着aiohttp和aiofiles的使用是必不可少的,那么我们现在就安装这两个模块到pycharm上 安装 将下面两行代码放入到pycharm上的终端就会开始下载 pip install aiohttp pip in…

我的企业证书是正常的但是下载应用app到手机提示无法安装“app名字”无法安装此app,因为无法验证其完整性解决方案

我的企业证书是正常的但是下载应用app到手机提示无法安装“app名字”无法安装此app,因为无法验证其完整性解决方案 首先,确保您从可信任的来源下载并安装企业开发者签名过的应用程序。如果您不确定应用程序的来源,建议您联系应用程序提供者…

宠物医院必备,介绍一款宠物疫苗接种管理软件

在当今社会,养宠物已经成为越来越多人的生活方式,宠物疫苗接种已是宠物医院的重要工作,但是目前绝大多数的宠物医院对疫苗接种的管理,还是采取人工登记方式,不仅效率低下,而且无法做到疫苗接种到期自动提醒…

vcruntime140.dll如何修复,快速修复vcruntime140.dll丢失的三种方法

vcruntime140.dll是Visual C 2015运行库的一个组件,它包含了许多运行时函数,用于支持各种程序的正常运行。当vcruntime140.dll文件丢失时,可能会导致一些程序无法正常运行。本文将详细介绍vcruntime140.dll的作用、丢失原因以及三种修复方法。…

面试问到MySQL模块划分与架构体系怎么办

面试问到Mysql模块划分与架构体系怎么办 文章目录 1. 应用层连接管理器(Connection Manager)安全性和权限模块(Security and Privilege Module) 2. MySQL服务器层2.1. 服务支持和工具集2.2. SQL Interface2.3. 解析器举个解析器 …

ISP图像信号处理——白平衡校正和标定介绍以及C++实现

从数码相机直接输出的未经过处理过的RAW图到平常看到的JEPG图有一系列复杂的图像信号处理过程,称作ISP(Image Signal Processing)。这个过程会经过图像处理和压缩。 参考文章1:http://t.csdn.cn/LvHH5 参考文章2:htt…

基于matlab创作简易表白代码

一、程序 以下是一个基于MATLAB的简单表白代码: % 表白代码 clc; % 清除命令行窗口 clear; % 清除所有变量 close all; % 关闭所有图形窗口 % 输入被表白者的名字 name input(请输入被表白者的名字:, s); % 显示表白信息 fprintf(\n); fprintf(亲爱的…

IDEA Rogstry中找不到compiler.automake.allow.when.app.running问题解决

网上大部分人教我们 先 File > Settings 然后 勾选 Build 下的 Compiler中的 Build project automatically 这些步骤都不会有问题 然后就会让我们 ctrl shift alt / 点 Rogstry 打开后 我人就麻了 根本没有什么 compiler.automake.allow.when.app.running 也不用慌 我们…

基于 SpringBoot 2.7.x 使用最新的 Elasticsearch Java API Client 之 ElasticsearchClient

1. 从 RestHighLevelClient 到 ElasticsearchClient 从 Java Rest Client 7.15.0 版本开始,Elasticsearch 官方决定将 RestHighLevelClient 标记为废弃的,并推荐使用新的 Java API Client,即 ElasticsearchClient. 为什么要将 RestHighLevelC…

Android 进阶——系统启动之BootLoader 及内核启动一(下)

文章大纲 引言一、Android 系统启动流程概述1、手机电源被打开时,首先是引导进入BootLoader分区2、BootLoader分区加载Linux 内核3、内核解析执行init.rc脚本并启动进程id为1 的init进程4、init进程初始化各种Android系统服务、ServiceManager以及Zygote 进程孵化器…

键盘上F1至F12键的作用

多年来,我们习惯了最上排的12个按键,从F1到F12,它们被称为“快速功能键”,可以让你更轻松地操作电脑;但是,很多人可能从未使用过它们,也从来不知道它们的用途。那么今天,就向大家科普…