之前的分析: 《Postgresql源码(57)HOT更新为什么性能差距那么大?》 相关 《Postgresql源码(32)Btree索引分裂前后结构差异对比》
1 概要
复习HOT更新流程:
内容主要分为两部分:
- 索引查询
- HOT更新
关于索引查询借用之前的一张图:
关于HOT更新引用www.interdb.jp的一张图:
2 场景构造
149369条数据刚刚好索引从两层升级为三层:
代码语言:javascript复制create table t8(id int primary key, info text);
truncate t8;
insert into t8 select generate_series(1,149370), md5(random()::text);
HOT更新哪里合适:更新最后一个页面的数据有空闲位置。
代码语言:javascript复制-- select relpages from pg_class where relname = 't8'; -- 1245
update t8 set info='87b5696b76a041a41495da0000000000' where id = 149370;
数据页面结构
索引页面结构
3 hot update
update t8 set info='87b5696b76a041a41495da0000000000' where id = 149370;
3.1 走索引找到数据
执行器进入索引堆栈的路径:
爬索引树参考之前的分析:https://cloud.tencent.com/developer/article/2000739
3.2 找到数据后进入update堆栈
找到数据后,进入ExecUpdate前必须拿到元组的ItemPointer,即元组的位置
代码语言:javascript复制ExecModifyTable
context.planSlot = ExecProcNode(subplanstate); // 递归语法树进行indexscan找到元组
// 返回
// TupleTableSlot
// {type = T_TupleTableSlot, tts_flags = 16, tts_nvalid = 2, tts_ops = 0xcbb240 <TTSOpsVirtual>,
// tts_tupleDescriptor = 0x23b29a0, tts_values = 0x23b2cb0,
// tts_isnull = 0x23b2cc0, tts_mcxt = 0x23b20f0,
// tts_tid = {ip_blkid = {bi_hi = 65535, bi_lo = 65535}, ip_posid = 0}, tts_tableOid = 0}
ExecGetJunkAttribute(slot, // tts封装的元组,前面索引查询的结果
resultRelInfo->ri_RowIdAttNo // 2:表示第二列
);
返回
代码语言:javascript复制ExecModifyTable
...
ExecUpdate(...,{ip_blkid = {bi_hi = 0, bi_lo = 1244}, ip_posid = 90},...)
3.3 开始update
代码语言:javascript复制TM_Result
heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
CommandId cid, Snapshot crosscheck, bool wait,
TM_FailureData *tmfd, LockTupleMode *lockmode)
{
...
第一步:计算hot_attrs标记所有索引列位置
hot_attrs = 0100000000
- 00000000:表示系统列站位
- 01:表示第一列上有索引
hot_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_ALL);
key_attrs = RelationGetIndexAttrBitmap(relation, INDEX_ATTR_BITMAP_KEY);
id_attrs = RelationGetIndexAttrBitmap(relation,
INDEX_ATTR_BITMAP_IDENTITY_KEY);
interesting_attrs = NULL;
interesting_attrs = bms_add_members(interesting_attrs, hot_attrs);
interesting_attrs = bms_add_members(interesting_attrs, key_attrs);
interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
...
block = ItemPointerGetBlockNumber(otid);
buffer = ReadBuffer(relation, block);
page = BufferGetPage(buffer);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
第二步:逻辑位置转换为物理偏移
逻辑位置:
ItemPointer={ip_blkid = {bi_hi = 0, bi_lo = 1244}, ip_posid = 90}
1244号页面,第90个item pointer。
物理位置:
(gdb) p ((PageHeader)page)->pd_linp89
$59 = {lp_off = 2432, lp_flags = 1, lp_len = 61}
代码语言:javascript复制 lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid));
oldtup.t_tableOid = RelationGetRelid(relation);
oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp);
oldtup.t_len = ItemIdGetLength(lp);
oldtup.t_self = *otid;
newtup->t_tableOid = RelationGetRelid(relation);
第三步:找到哪些索引列被更新了
interesting_attrs传入是为0100000000在函数内被破坏掉了。
传出的modified_attrs=0,因为根据更新的元组,发现没有索引列被更新了。
代码语言:javascript复制 modified_attrs = HeapDetermineColumnsInfo(relation, interesting_attrs,
id_attrs, &oldtup,
newtup, &id_has_external);
第四步:新元组也可以放到旧页面上且没有更新任何索引列
modified_attrs = 0:索引列没有更新的。
hot_attrs = 0100000000:索引列位置。
可以做HOT更新:
use_hot_update = true;
代码语言:javascript复制 if (newbuf == buffer)
{
if (!bms_overlap(modified_attrs, hot_attrs))
use_hot_update = true;
}
else
{
/* Set a hint that the old page could use prune/defrag */
PageSetFull(page);
}
if (use_hot_update)
{
第五步:标记HEAP_HOT_UPDATED完成Insert
增加标记位HEAP_HOT_UPDATED、HEAP_ONLY_TUPLE
代码语言:javascript复制 HeapTupleSetHotUpdated(&oldtup);
HeapTupleSetHeapOnly(heaptup);
HeapTupleSetHeapOnly(newtup);
}
RelationPutHeapTuple(relation, newbuf, heaptup, false); /* insert new tuple */