参考内容
PostgreSQL 数据页Page解析(1)- 基础
PostgreSQL Page页结构解析(3)- 行数据
PostgreSQL Page页结构解析(5)- B-Tree索引存储结构
知识点:
- LSN:日志序列号
- 表的位置 base/25835/1245 对应 ‘’表空间/数据库oid/表oid‘’
- 表对应vm,fsm:(用于多版本控制)
- vm(可见性映射):为每个数据块设置一个标志位,标记块中是否没有需要清理的行,用于加快vacuum清理,用于lazy vacuum
- fsm(空闲空间映射):将每个块(8k)分成256份,用一个字节表示一块剩余大小。一个表最多2^32个块,数据结构由分三层二叉树存储。在执行vacuum操作时或插入操作需要扫描时创建。
- hexdump命令:hexdump -C 二进制文件目录 -s 起始字节 -n 几个字节
- X86使用小端模式
页结构![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/ffaeedaef49a13d8d17d79f3f9758eaf.png)
- 特殊空间用于存储索引访问使用的数据,不同的访问方法数据不同
一行的头部保留信息:
- xmin 创建的事务id
- xmax 最新的事务id
- cid / xvac 插入删除的命令id或vaccum事务的id
- ctid 当前版本与新版本的指针(块号,偏移号)
- infomask2 特征数量、flag
- infomask flag
- off 元组位置offset
- bits 用来标记空属性的bitmap
B树索引
-
索引页头部与数据页头部一样( 24个字节 )
-
索引数据: IndexTupleData(指针(块号,偏移号)、bitinfo(hasnull、hasvar-width、元组大小))+Bitmap+Value
-
页最后的特征空间(16字节):left、right、从叶节点起向上第几层、flag
-
索引头信息有序,索引数据信息无序
-
root / branch 块与 leaf 块用 btpo_flags 来标记(在页特殊空间):leaf的ctid指向 heap table block,叶块当btpo_next 不是0时,意味不是最右块,则第一个数据存的是最大key(highkey),第二个起才是由小到大排列的key
参考内容
PostgreSQL 源码解读(1)- 插入数据(heap_insert)
知识点:
-
buffer 是块号的标记,是一个数,对于localbuffer,buffer是负数,对应 LocalBufferBlockPointers 块的序号是 -buffer-1,对于非localbuffer,buffer是正数,本身即序号,对应 BufferBlocks 的 (buffer-1)* BLCKSZ 的位置。
-
本地 buffer 用于临时表的读写。 共享buffer用于多线程并发。
-
共享buffer由缓冲区块、缓冲区描述器以及引用计数器组成,主要涉及函数:
- InitBufferPool
- 分配 缓冲区描述器和缓冲区块分配空间
- 初始化1000个描述器,每个描述器的next都指向下一个
- BufferAlloc
- 从Hash表找相应缓冲区,有就加pin,等待io结束后将它返回
- 没有对应缓冲区就用替换机制找一个缓冲区
- 但如果这个缓冲区是脏的,且有排他锁,就需要再换一块,如果没有排他锁就把脏块写出
- 得到的缓冲区如果被其它进程占用,就要重新用替换机制找一个缓冲区了,没有占用就可以直接返回
- InitBufferPool
-
页面替换的 ClockSweep 机制
按圈一个个找引用计数为0的,对它的usage_count减1,如果usage_count为0,就可以选择这块。usage_count是考虑使用次数多,意味着被其它进程引用的可能性大,最好不要选这样的块。
heap_insert函数
PageAddItem:
- 如果指定了偏移,就覆盖数据(检查下能不能覆盖)或者把这后面的itemid向后挪出一个itemid的校园
- 如果没指定就找有无回收空间
- 如果没回收空间就在空闲空间插入itemid
- itemid指向在空闲空间后面的位置,copy数据到这个位置,并修改页头的upper与lower指针
注:空闲空间不是马上清除,要等vacuum线程统一清理
RelationPutHeapTuple
- 根据 buffer (块号)获取块
- 执行 pageAddItem
- 更新 元组指针 itemid 的 tcid
heap_prepare_insert
- 设置元组头部的oid,infomask中包含insert的bit,cmin为当前命令cid,xmax为0(初始版本),表oid。
- 对于可以toast且元组长度大于threshold的元组调用toast_insert_or_update返回toast的版本。
RelationGetBufferForTuple
- 计算元组需要预留的大小,加上元组大小不能超过元组最大大小
- 要插入的页尽可能是刚插入过的页,没有的话就从fsm上获取一页,如果没有fsm信息或fsm不够大,就获取关系表的最后一页
- 为获取的页加锁
- 检查是否pin和lock成功了这些页,并保证成功(pin操作是在vm里)
- 检查空间是否足够,如果空间够了,就返回这个buffer,如果空间不够,就重新获取
- 如果fsm中都不够用,就扩展表
- 对于扩展后的表,要将页初始化,再返回这个buffer
参考内容
PostgreSQL 源码解读(5)- 插入数据#4(ExecInsert)
知识点
- with check option 用于创建视图,保证插入删除与更新必须满足创建视图时的条件。
- 虚拟元组:只有属性值的数组,不是真的元组结构
ExecInsert
ExecMaterializeSlot:用属性的值生成元组结构,在到相应slot
- 如果slot上有本地heaptuple,就在slot的context上分配HeapTupleBuffer,包括:元组长度、itemId、元组头,把这个heaptuple复制到slot的buffer上,释放原buffer
- slot上没有heaptuple,就获取所有属性的值,存在slot->PRIVATE_tts_values[i]上(虚拟元组)
- 根据虚拟元组生成真正的元组结构(heaptuple_form_to),填充在 slot 的本地 heaptuple,并让buffer指向它
ExecBRInsertTriggers :插入前trigger
- 遍历表的trigger:relinfo->ri_TrigDesc->triggers[i]
- 对每个 trigger 都用旧元组与trigger函数得到新元组:newtuple = ExecCallTriggerFunc(LocTriggerData, relinfo->ri_TrigFunctions, estate->context),其中 TriggerData 中包含了oldtuple,释放old元组
- 在estate->es_trig_tuple_slot上设置新元组描述符(ExecSetSlotDescriptor)并把元组放上去(ExecStoreTuple)
ExecIRInsertTriggers :与上同,是Instead时的事件
ExecWithCheckOptions:执行ExecQual来检查约束
ExecConstraints:检查非空限制与表限制
ExecCheckIndexConstraints:unique限制与exclusion限制
ExecOnConflictUpdate:
- 获取更新锁 heap_lock_tuple
- 检查元组可见性 ExecCheckHeapTupleVisible
- 把已有元组放在特定slot(mtstate->mt_existing,)中 ExecStoreHeapTuple
- 判断是否满足冲突条件 ExecQual
- 不冲突的话执行 withCheck 的检查
- 然后执行投影,放在特定slot上(resultRelInfo->ri_onConflictSetProj->pi_slot)
- 然后执行更新 ExecUpdate
HeapTupleSatisfiesUpdate:判断元组被占用状态
- xmin未提交
- xmin是invalid(有这个标记),返回 HeapTupleInvisible
- xmin是当前事务的
- cmin比当前命令id大,返回 HeapTupleInvisible
- xmax是invalid(有这个标记)
- 现在可能别的事务正在用,返回 HeapTupleMayBeUpdated
- 元组只是标记为 HEAP_XMAX_LOCK_ONLY,并不update
- 如果xmax就一个,就判断是否InProgress,返回 HeapTupleBeingUpdated,否则返回 HeapTupleMayBeUpdated
- 如果xmax是多个事务(有这个标记),就要判断每一个了:从 pg_upgraded 获取更新它的事务,这些事务可能是自己,也可能是其它事务,
- 如果元组没有这个标记,但是多事务,就取出update的事务,是自己,返回 HeapTupleSelfUpdated 或 HeapTupleInvisible
- xmin正在进行,返回 HeapTupleInvisible
- xmin确实提前了的话,就标记上,否则它已经abort了,返回 HeapTupleInvisible
参考内容
PostgreSQL 源码解读(6)- 插入数据#5(ExecModifyTable)
ExecModifyTable 是插入,更新,删除共同的入口
参考内容
PostgreSQL 源码解读(13)- 插入数据#12(PostgresMain)
知识点
c语言中的try catch可以用setjmp 与 longjmp 实现,setjmp(b) b中保存这时堆栈中的函数地址,longjmp(b, value)时,程序会运行到setjmp的位置,并返回value值,这样就可以通过value来判断错误类型
postgresmain
参考内容
PostgreSQL 源码解读(19)- 查询语句#4(ParseTree详解)
知识点
parseTree中的节点:
RangeSubselect:from 的子查询
RangeVar:JoinExpr 中的左右表节点
TargetList:select 的 fields
ResTarget:parseTree 中 TargetList 中的元素
当 SelectStmt 中 op 为 UNION,子查询中将有 larg 与 rarg
ColumnRef:parseTree 中的列,其 field 是一个 List,由表名与列名组成
QueryTree中的节点:
RangeTblEntry:from 的表
RangeTblRef:JoinExpr 中的左右表节点,是RangeTblEntry在rtable中的序号
TargetEntry:QueryTree 中的列
参考内容
PostgreSQL 源码解读(20)- 查询语句#5(查询树Query详解)
pg_analyze_and_rewrite 对select做的事
-
把涉及到的表与子查询都放在 rtable 中,结构为 RangeTblEntry,from、join、qual 等地方用到表时都变成对 rtable 的引用 RangeTblRef,有 fromlist,targetlist,joinlist,操作等。
-
target 转为 TargetList 中的 TargetEntry
-
jointree 中有 FromExpr 与 qual 条件,FromExpr 是一个列表,每个表项可以是 RangeTblRef 指向RangeTblEntry,也可以是 joinExpr
-
RangeTblEntry 中表的别名为 alias,列的别名在 eref 的 List 里
-
qual 是 OpExpr 节点,其中对于有常量的表达式有 args(为 RelabelType->Var 和 Const 节点),对于双变量表达式有 args (双 RelabelType->Var 节点)和 funcid,这个函数可以是简单的“=”函数
参考内容
PostgreSQL 源码解读(21)- 查询语句#6(PlannedStmt详解-跟踪分析)
知识点
如何开启跟踪日志?postgresql.conf配置文件设置参数:
log_destination = 'csvlog'
log_directory = 'pg_log' #与postgresql.conf文件在同一级目录
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 2d
log_rotation_size = 100MB
#
debug_print_parse = on #打印parse树
debug_print_rewritten = on #打印parse rewrite树
debug_print_plan = on #打印plan树
debug_pretty_print = on #以pretty方式显示
pg_plan_queries 中做了什么?
- 把所有涉及到的 RangeTblEntry 放在 root(PlannedStmt )的 rtable 上,涉及的表在 relationOids 上,生成的计划放在 PlannedStmt 的 planTree 节点
- 对于 NestLoop 节点,有 MergeJoin 、 SeqScan、HashJoin,三种节点均有RangeTblEntry 在 rtable 中的序号