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

转载于:https://www.cnblogs.com/flying-tiger/p/8029941.html

Postgres中tuple的组装与插入相关推荐

  1. 在Postgres中为插入语句生成UUID?

    本文翻译自:Generating a UUID in Postgres for Insert statement? My question is rather simple. 我的问题很简单. I'm ...

  2. postgresql 15源码浅析(1)—— postgres中的1号数据库

    摘要 在创建数据库集簇后,该集簇中默认会包含三个系统数据库template1.template0和postgres,其中template0和postgres都是在初始化过程中从template1复制出 ...

  3. C#中Tuple的使用

    定义:元组是具有 特定数量和序列 的元素 的数据结构  (注意断句哈!) 元组通常有四种使用方式︰ 一.表示一组数据 例如,一个元组可以表示一条数据库记录,并且每一个分量对应表示这条记录的每个字段便于 ...

  4. Java编号姓名元宝数密码,通过my Eclipse控制台向数据库(SQL2008)中查找、删除、插入信息...

    通过my Eclipse控制台向数据库(SQL2008)中查找.删除.插入信息如果编译程序有什么错误还望大家多多指正代码执行所需数据库.架包及java源文件已上传至文件 文件名 SQl_JDBC.zi ...

  5. ef oracle 批量更新慢_详解Oracle中多表关联批量插入、批量更新与批量删除

    概述 今天主要介绍一下Oracle数据库中多表关联批量插入.多表关联批量更新和多表关联批量删除.下面用实验来理解下~ 一.创建必须的表和序列语句 --创建部门表 dept:CREATE TABLE d ...

  6. 数据结构:单链表中在P结点前插入S结点

    标题:数据结构:单链表中在P结点前插入S结点 在数据结构的习题中偶然看到了一个题目 已知在单链表中P不是首元结点也不是尾元结点,在P结点前插入S结点 #include<stdio.h> # ...

  7. 关于mavon-editor中iframe 的使用 和插入视频、音频的记录

    一.问题 我想将视频.音频运用外链的形式iframe插入自己的博客文章中,节约服务器资源 插入后mavon-editor预览区不显示,没有解析出来 二. 解决 搞了三四天,在mavon-editor官 ...

  8. mysql中常用的三种插入数据的语句

    mysql中常用的三种插入数据的语句: insert into表示插入数据,数据库会检查主键(PrimaryKey),如果出现重复会报错: replace into表示插入替换数据,需求表中有Prim ...

  9. 火车头采集器文章翻译插件(文章标题内容中英双语对照|自动插入相关图片)

    火车头采集器文章翻译插件(文章标题内容中英双语对照|自动插入相关图片) 为了保护接口压力防止被封IP: 请把采集的间隔时间调整为10000~100000 火车头采集器文章翻译插件(文章标题内容中英双语 ...

最新文章

  1. oracle 常用sql
  2. 并发测试mysql_Jmeter性能测试系列——结果分析与报告输出
  3. 17、Java Swing Timer:计时器组件
  4. docker之数据卷管理
  5. vc 代码检查工具_C++网络安全入侵检测技术模块及源程序代码
  6. 为什么姜黄素+胡椒碱会让姜黄素吸收率增加2000%以上
  7. TCP服务器端和客户端建立连接 - 服务器端的回调处理
  8. C++函数模板和普通函数的调用规则
  9. libwacom9 : Depends: libwacom-common (= 2.2.0-1) but 1.12-1 is to be installed
  10. 信息学奥赛一本通(1178:成绩排序)——选择排序
  11. 微课|玩转Python轻松过二级(2.4节):常用内置函数用法精要1
  12. python购物车----运维开发初学
  13. 齐鲁工业大学计算机学院复试名单,齐鲁工业大学2019年硕士研究生拟录取名单公示...
  14. Unity报错(bug)
  15. CSS3的2D、3D变换、过度与动画效果
  16. 王者nba服务器维护,王者NBA新手常用问题FAQ大全
  17. JavaApplet运行
  18. 360浏览器极速模式自动_浏览器正在为网站带来自动暗模式
  19. C# Dev GridView自定义底部统计单元格
  20. 机器学习之k-means算法详解

热门文章

  1. 【URAL - 1114 】Boxes (dp,组合数学)
  2. C 的Pair用法分类整理(精)
  3. 图解算法学习笔记(目录)
  4. createform用法_vue自定义表单生成器form-create使用详解
  5. mysql瓶颈分析_网站瓶颈分析—MYSQL性能分析
  6. 较简单的字节输入流输出流拷贝文件
  7. 剑指offer之队列的最大值
  8. leetcode1069. 产品销售分析 II(SQL)
  9. Elasticsearch集群节点配置详解
  10. python生成的词云没有图案_还在为专栏封面发愁?我用Python写了个词云生成器!...