Postgres的tuple的组装
1.相关的数据类型
我们先看相关的数据类型:
HeapTupleData(src/include/access/htup.h)
typedef struct HeapTupleData
{
uint32 t_len; /* length of *t_data */
ItemPointerData t_self; /* SelfItemPointer */
Oid t_tableOid; /* table the tuple came from */
HeapTupleHeader t_data; /* -> tuple header and data */
} HeapTupleData;
HeapTupleHeaderData(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! */
uint16 t_infomask2; /* number of attributes + various flags */
uint16 t_infomask; /* various flag bits, see below */
uint8 t_hoff; /* sizeof header incl. bitmap, padding */
/* ^ - 23 bytes - ^ */
bits8 t_bits[FLEXIBLE_ARRAY_MEMBER]; /* bitmap of NULLs */
/* MORE DATA FOLLOWS AT END OF STRUCT */
};
t_choice具有2个成员的联合类型:
1.t_heap 用于记录对元组执行插入/删除操作事物ID和命令ID,这些信息主要用于并发控制是检查元组对事物的可见性
2.t_datum一个新的元组在内存中形成的时候,我们不关心事物的可见性,因此在t_choice中需要用DatumTupleFields结构来记录元组的长度等信息,把内存的数据写入到表文件的时候,需要在元组中记录事物和命令ID,因此会把t_choice所占的内存转换成HeapTupleFields结构并且填充响应数据后再进行元组的插入。
t_ctid用于记录当前元组或者新元组的物理位置,块号和块内偏移量,例如(0,1)第一个块内的第一个linp,若tuple被跟新,那么就记录新版本的物理位置。
t_infomask2使用其低11位标识当前tuple的attribute的个数,其他位用于HOT以及tuple可见性的标志位
t_infomask用于标识tuple当前的状态,比如是否有OID,是否空的字段,t_infomask每一位代表一种状态,总共16种。
2.Tuple的构造
构造tuple的函数(src/backend/access/common/heaptuple.c)
HeapTuple
heap_form_tuple(TupleDesc tupleDescriptor,
Datum *values,
bool *isnull)
该函数使用给定的values数组和isnull数组来组装生成一个tuple。
该函数的主要流程是先计算整个tuple所需要的长度(这个长度是指tuple中除掉HeapTupleData结构以外的长度。事实上,该长度存储在HeapTupleData的t_len的属性中。)然后以此申请内存,最后根据values和isnull来填充tuple数据。
我们稍微说一下这个t_len的计算。
len = offsetof(HeapTupleHeaderData, t_bits);
首先计算heaptupleheaderdata的长度,这个offsetof计算了从HeapTupleHeaderData的首址到它的成员变量t_bit的偏移量。
所以为什么不直接sizeof(HeapTupleHeaderData)呢?
原因是t_bits描述了NULL的bitmap关系,它的实际长度与列(属性)个数有关,是一个可变的值,
因此,在计算完HeapTupleHeaderData长度的时候,我们便根据是否存在着null列,来计算相应的数据(如下)。
if (hasnull)
len += BITMAPLEN(numberOfAttributes);
以及是否有oid:
if (tupleDescriptor->tdhasoid)
len += sizeof(Oid);
再加上padding大小(涉及到C语言的数据对齐):
hoff = len = MAXALIGN(len); /* align user data safely */
最后再获取data的长度:
data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
len += data_len;
获取了tuple的长度申请好内存后,向里面添加数据,就获得了如下的tuple(结构):

其中,hoff中包括了: 从TupleHeaderData起始位置到t_bits的位置;用户数据是从t_hoff开始,加上t_bits的偏移,以及oid的偏移,开始真正存储的。 这些由上图可以得知。
heap_fill_tuple 函数中依据tupledesc中atts所提供的信息来保存数据到相应的位置。att[i]->attlen == -1 当为此种情况时候,表明其是varlen数据,例如varchar之类的数量类型,att[i]->attlen == -2 当为此种情况时候,为cstring,即字符串形式的数据。never needs alignment 无需进行对齐操作。否则,为固定长度的类型。
如果是varlen类型数据时候。还需要使用VARATT_IS_EXTERNAL来判定是否是存储在外存上面。
做好了一条tuple之后,我们还要把它插入到数据库对应的表中才算完事。
3.Tuple的插入
插入tuple到heap的函数
Oid
heap_insert(Relation relation, HeapTuple tup, CommandId cid,
int options, BulkInsertState bistate)
这个函数还挺复杂的,涉及到了内存和disk的数据交换。内存主要涉及到了缓冲区buffer和lock,对于disk涉及到了FSM映射表和Page。
首先,预处理函数设置元组头部的字段,分配一个OID,并在必要时为元组提供Toast。请注意,在这里heaptup是传进来的tuple,而变量tup是作为一个临时变量存在的。
heaptup = heap_prepare_insert(relation, tup, xid, cid, options);
我们要将元组插入到page,涉及到内存和disk的数据交换,这就要用到buffer。我们知道insert的本质也是先"select"再"insert"。也就是说我们先要找到该表上合适的Page来装这个tuple。因此,我们为该Page申请一个buffer并加上执行锁,将该Page载入申请到的buffer中。注意,此时要插入的tuple并未写到buffer中。
buffer = RelationGetBufferForTuple(relation, heaptup->t_len,
InvalidBuffer, options, bistate,
&vmbuffer, NULL);
这样以后,所有的准备工作都做好了,就差临门一脚了。成与不成就在一举了。是不是听起来有点。。。?
是的,我们要进入临界区了,谁都不要打扰我:
START_CRIT_SECTION();
这个语句其实是设置了全局变量CritSectionCount,就相当于信号量了,这里不多说。
然后我们开始写数据吧:
RelationPutHeapTuple(relation, buffer, heaptup,
(options & HEAP_INSERT_SPECULATIVE) != 0);
但是话说,真的写了?并没有!你忘了我们postgresql有WAL么?你WAL log都还没写,数据怎么能先到磁盘?
那么这里我们有什么?我们buffer里面有Page,我们"手上"有tuple,好的,我们把tuple放到这个buffer装的Page里面对应的位置上。
就是说,我们的数据还在buffer里。
那么怎么通知Postgres我有脏数据要写啊?
MarkBufferDirty(buffer);
设置buffer为脏,这样Postgres在下次写磁盘(checkpointer)的时候就知道把这个buffer里的数据丢回disk了。
那么,我们也就知道了,接下来我们就要开始准备WAL和数据了。
这里大致用到了这几个函数:
XLogBeginInsert
XLogRegisterData
XLogRegisterBuffer
XLogRegisterBufData
PageSetLSN
好的,WAL也设置好了。(只等插入这条tuple的命令commit之后,WAL数据立即落盘,写到disk上,也就是pg_xlog目录下的WAL段里面。)此时退出临界区。
这个时候要放开buffer了。
最后我们再做一做清理工作,打完收工。
最最最后,实际的元组仍然在内存,不过没事,因为你的查询也是要先走buffer和cache的,所以你已经可以查询到这条数据了。等到系统调用了checkpointer进程,你的数据才真正落了盘,然而,这对你是透明的。
这里关于数据落盘的先后顺序和时机,我还是借网上的两张图吧:
WAL和data进入buffer的时机:

WAL和data写到disk的时机:

好的就是这样~
恩,这次对WAL的插入的分析比较简略,下次我弄清楚了再细说吧各位。
参考文章:
http://blog.jobbole.com/106585/
http://www.cnblogs.com/sangli/p/6404771.html
http://www.jianshu.com/p/a37ceed648a8
Postgres的tuple的组装的更多相关文章
- Postgres中tuple的组装与插入
1.相关的数据类型 我们先看相关的数据类型: HeapTupleData(src/include/access/htup.h) typedef struct HeapTupleData { uint3 ...
- Python~函数的参数
def func(a,b,c,*args,**kw): print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw) 必选参数,默认参数,可变参数,关键字参数 d ...
- Storm中关于Topology的设计
一:介绍Storm设计模型 1.Topology Storm对任务的抽象,其实 就是将实时数据分析任务 分解为 不同的阶段 点: 计算组件 Spout Bolt 边: 数据流向 数据从上 ...
- MySQL and Postgres command equivalents (mysql vs psql)
MySQL and Postgres command equivalents (mysql vs psql) 博客分类: Database From: http://blog.endpoint.c ...
- Following a Select Statement Through Postgres Internals
This is the third of a series of posts based on a presentation I did at the Barcelona Ruby Conferenc ...
- Discovering the Computer Science Behind Postgres Indexes
This is the last in a series of Postgres posts that Pat Shaughnessy wrote based on his presentation ...
- Tuple
Tuple(组元)是C# 4.0引入的一个新特性,编写的时候需要基于.NET Framework 4.0或者更高版本. 在以前编程中,当需要返回多个值得方法中,常常需要将这些值放置到一个结构体或者对象 ...
- 再说Postgres中的高速缓存(cache)
表的模式信息存放在系统表中,因此要访问表,就需要首先在系统表中取得表的模式信息.对于一个PostgreSQL系统来说,对于系统表和普通表模式的访问是非常频繁的.为了提高这些访问的效率,PostgreS ...
- Postgres中的物化节点之sort节点
顾名思义,物化节点是一类可缓存元组的节点.在执行过程中,很多扩展的物理操作符需要首先获取所有的元组后才能进行操作(例如聚集函数操作.没有索引辅助的排序等),这时要用物化节点将元组缓存起来.下面列出了P ...
随机推荐
- myeclipse10破解失败,正解
此文图借用他人的,很是经典 第一步:输入任意用户名 第二步:点击Systemid... 按钮,自动生成本机器的systemid.ps:确定本机的systemid,如果和破解器生成不一致,需要copy本 ...
- 版本12.2.0.1.0数据库,复制种子数据库快速创建租户数据库PDB
实验测试:快速创建一个数据库PDB2: 实验环境:12.2.0.1.0版本数据库,dbca图形化安装,现有环境,CDB容器数据库ORCL,PDB可插拔数据库ABC ---查询CDB名称,状态 SQ ...
- cloneNode克隆节点在不同浏览器的差异
cloneNode是用于克隆节点的,如果待克隆的节点还有子节点以及自定义属性.添加的有事件,那么克隆时,可以指定是克隆节点本身,还是将其所有子节点信息也克隆进去,这是通过给cloneNode传递一个布 ...
- vue初级学习--控制台创建vue项目
一.导语 困困的我,好想睡啊,但还是敲下这边有的没的的笔记吧 二.正文 1.输入 vue list 可查看此环境下,vue能创建的项目模板 没将webpack.vue-route啃精了的话,老实用we ...
- HTML学习笔记 CSS学习选择器案例 第五节 (原创) 参考使用表
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- [Redis源码阅读]sds字符串实现
初衷 从开始工作就开始使用Redis,也有一段时间了,但都只是停留在使用阶段,没有往更深的角度探索,每次想读源码都止步在阅读书籍上,因为看完书很快又忘了,这次逼自己先读代码.因为个人觉得写作需要阅读文 ...
- 八:Lombok 安装、入门 - 消除冗长的 java 代码
Lombok 安装.入门 - 消除冗长的 java 代码 前言: 逛开源社区的时候无意发现的,用了一段时间,觉得还可以,特此推荐一下. lombok 提供了简单的注解的形式来帮助我们简化消 ...
- js的call() ,apply() 两种方法的区别和用法,最白话文的解释,让枯燥滚粗!
百度了一圈calll()函数和apply()函数,感觉还是糊里糊涂 正好我前几天刚又重新翻了一遍 那本 600多页 的圣经书,我习惯时不时的去打下基础,只是为了用来装逼,给人讲解....(我是有多蛋疼 ...
- 手工搭建基于ABP的框架(3) - 登录,权限控制与日志
为了防止不提供原网址的转载,特在这里加上原文链接: http://www.cnblogs.com/skabyy/p/7695258.html 本篇将实现登录.权限控制.日志配置与审计日志的功能.首先我 ...
- python 小白(无编程基础,无计算机基础)的开发之路 day2
本节内容 列表.元组操作 字符串操作 字典操作 集合操作 文件操作 字符编码与转码 1. 列表.元组操作 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 ...