diff --git a/app/zh/blogs/wangfeihuo/images/image-20240817134646546.png b/app/zh/blogs/wangfeihuo/images/image-20240817134646546.png new file mode 100644 index 0000000000000000000000000000000000000000..874e4c846621f6ebfb829db8521d9f22bef55192 Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240817134646546.png differ diff --git a/app/zh/blogs/wangfeihuo/images/image-20240817135320344.png b/app/zh/blogs/wangfeihuo/images/image-20240817135320344.png new file mode 100644 index 0000000000000000000000000000000000000000..0bd017f7b51a81e23708228c9aebda89e61f34ab Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240817135320344.png differ diff --git a/app/zh/blogs/wangfeihuo/images/image-20240824223107903.png b/app/zh/blogs/wangfeihuo/images/image-20240824223107903.png new file mode 100644 index 0000000000000000000000000000000000000000..63a42cf3e38797956ceb494aff303108ab5bcb20 Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240824223107903.png differ diff --git a/app/zh/blogs/wangfeihuo/images/image-20240826001425734.png b/app/zh/blogs/wangfeihuo/images/image-20240826001425734.png new file mode 100644 index 0000000000000000000000000000000000000000..2b7d1f48151e78f214cf743bc499dcf2a91e0edf Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240826001425734.png differ diff --git a/app/zh/blogs/wangfeihuo/images/image-20240901155038950.png b/app/zh/blogs/wangfeihuo/images/image-20240901155038950.png new file mode 100644 index 0000000000000000000000000000000000000000..aa8d2277f85cf48d3797a6478c0d55061c94ff49 Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240901155038950.png differ diff --git a/app/zh/blogs/wangfeihuo/images/image-20240901161552628.png b/app/zh/blogs/wangfeihuo/images/image-20240901161552628.png new file mode 100644 index 0000000000000000000000000000000000000000..e389d9735a2dbd87b27a51396f03899318c879c7 Binary files /dev/null and b/app/zh/blogs/wangfeihuo/images/image-20240901161552628.png differ diff --git "a/app/zh/blogs/wangfeihuo/openGauss CTE Reuse\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" "b/app/zh/blogs/wangfeihuo/openGauss CTE Reuse\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" new file mode 100644 index 0000000000000000000000000000000000000000..d69e5a0b7b9c34c4a2e084b3c0c0e020ca650a3e --- /dev/null +++ "b/app/zh/blogs/wangfeihuo/openGauss CTE Reuse\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" @@ -0,0 +1,70 @@ +--- +title: 'openGauss CTE Reuse原理代码走读' +date: '2024-09-01' +category: 'blog' +tags: ['openGauss CTE Reuse原理代码走读'] +archives: '2024-09' +author: 'wangfeihuo' +summary: 'openGauss CTE Reuse原理代码走读' +img: '/zh/blogs/wangfeihuo/title/opengauss.png' +times: '9:30' +--- +##### 一. 前言 + +​ CTE 是指with的公共表达式,如下所示是个CTE样例: + +![image-20240901161552628](./images/image-20240901161552628.png) + +​ CTE表达式往往在同一条sql中多次被重复引用,如上图所示的cte被引用了两次(c1 和 c2),我们称为2个CTE实例。 + +​ 本文只要讲述在openGuass中,在sql中同一个CTE被多次引用时,数据是如何进行缓存和Reuse的。如上所示cte的c1和c2两个实例进行数据读取时,只要真正读取一次t11的表即可。 + + + +##### 二. CTE REUSE相关数据结构 + + 1. 相同的CTE用的是同一个Tuplestorestate,Tuplestorestate->memtuples中缓存着改CTE的数据,Tuplestorestate->memtupcount记录着缓存里边元组的个数。 + 1. 同一条sql中多次重复使用某CTE时,Tuplestorestate->readptrs记录着该CTE下每个CTE实例已经读取过的数据信息,readptrcount则记录着该CTE有多少个实例。 + 1. 每个CTE实例保存有一个readptrs指针记录着访问缓存的信息,其中readptrs->current字段记录着该CTE实例已经读到的缓存数据位置,readptrs->eof_reached记录着该实例是否已经读取到缓存数据的边界。 + +4. Tuplestorestate->activeptr是临时保存的CteScanState->readptr信息,每次需要操作CTE实例的时候,会把Tuplestorestate->activeptr置为CteScanState->readptr,所以记录的也即使当前操作的CTE实例。 + + + +##### 三. CTE Reuse 实现流程和代码走读 + +​ CTE Reuse的实现整理流程如下所示: + +![image-20240901155038950](./images/image-20240901155038950.png) + + + +代码走读如下所示: + +``` +CteScanNext + tuplestore_select_read_pointer(tuplestorestate, node->readptr); // node->readptr记录着是当前需要读取数据的CTE实例的ID,将此ID暂存在state->activeptr中 + eof_tuplestore = tuplestore_ateof(tuplestorestate); + state->readptrs[state->activeptr].eof_reached; + if (!eof_tuplestore) { // 如果对应的CTE实例还有缓存信息可以读取 + tuplestore_gettupleslot + tuplestore_gettuple + TSReadPointer* readptr = &state->readptrs[state->activeptr]; // 根据state->activeptr扎到对应CTE实例的readptr信息 + return state->memtuples[readptr->current++]; // 根据对用实例的readptr的current从缓存读取数据,并且readptr->current++,下次读取可以直接读取缓存中的下一条数据 + } + if (eof_tuplestore) { // 无更多的缓存数据 + ExecProcNode(node->cteplanstate); // 直接通过SeqScan读取元组数据 + tuplestore_puttupleslot(tuplestorestate, cteslot); + tuplestore_puttuple_common(state, (void*)tuple); + readptr = state->readptrs; + for (i = 0; i < state->readptrcount; readptr++, i++) { + if (readptr->eof_reached && i != state->activeptr) { + readptr->eof_reached = false; // 将除了当前CTE实例外的其他已经eof的实例的eof_reached设置为false,因为有新的数据进缓存了 + readptr->current = state->memtupcount; + } + } + + state->memtuples[state->memtupcount++] = tuple; // 将当前seqscan读到的数据保存到缓存中,并且将缓存的数量state->memtupcount++ + } +``` + diff --git "a/app/zh/blogs/wangfeihuo/openGauss blks_hit\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" "b/app/zh/blogs/wangfeihuo/openGauss blks_hit\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" new file mode 100644 index 0000000000000000000000000000000000000000..a647dbcb9530420dbbc480423b3bd85335b8e4f0 --- /dev/null +++ "b/app/zh/blogs/wangfeihuo/openGauss blks_hit\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" @@ -0,0 +1,140 @@ +--- +title: 'openGauss blks_hit实现原理代码走读' +date: '2024-09-01' +category: 'blog' +tags: ['openGauss blks_hit实现原理代码走读'] +archives: '2024-09' +author: 'wangfeihuo' +summary: 'openGauss blks_hit实现原理代码走读' +img: '/zh/blogs/wangfeihuo/title/opengauss.png' +times: '9:30' +--- + +##### 一. 前言 + +​ 在openGauss 中,select * from pg_stat_database可以看到每个数据库缓存命中率(blks_hit)的统计,如下所示: + +![image-20240817134646546](./images/image-20240817134646546.png) + +​ 本文主要走读openGuass的代码来了解openGuass是在怎样进行缓存命中率的统计的。 + + + +##### 二. 数据库缓存命中率(blks_hit)统计实现流程 + +​ 在openGauss 中,每一个表(Relation)的结构体有一个字段pgstat_info,pgstat_info->t_counts中会存放着relation的各种统计信息,如元祖的读取数量,缓存的命中数量等,如下是整个代码的实现流程。 + +​ 1. 首先在回话初始化的时候,pgstat_initstats函数会在回话中,创建pgstat_info的内存空间并且赋值给relation->pgstat_info,代码流程如下所示: + +``` +pgstat_initstats + rel->pgstat_info = get_tabstat_entry + hash_entry = (TabStatHashEntry*)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key); + return entry // entry就是relation的pgstat_info +``` + +​ 2. 在读取数据块的时候,传入realtion,并且根据在buffer_read_extended_internal判断读取的是缓存还是磁盘来对改relation的t_blocks_hit的统计之,代码流程如下所示: + +``` +buffer_read_extended_internal + ReadBuffer_common(...&hit...); + bufHdr = BufferAlloc(&found); + if (found) { + *hit = true; // 如果需要读取的block在buff中能够找到,则将hit置上 + } + if (hit) { + pgstat_count_buffer_hit(reln); // 如果hit被置上,则对改relation的t_blocks_hit统计信息进行加1 + } +``` + + 3. 在StatementFlush现成中定位将定期发送将所有表的统计信息发送给PgstatCollectorMain现成汇总: + + ``` + StatementFlush + pgstat_report_stat + for (tsa = u_sess->stat_cxt.pgStatTabList; tsa != NULL; tsa = tsa->tsa_next) { + for (i = 0; i < tsa->tsa_used; i++) { + memcpy_s(&this_ent->t_counts, &entry->t_counts) + pgstat_send_tabstat(this_msg); + } + } + ``` + +4. PgstatCollectorMain收到信息之后,第db下所有的包的t_blocks_hit进行汇总,并且存放着u_sess->stat_cxt.pgStatDBHash中 + +``` +pgstat_recv_tabstat + dbentry = pgstat_get_db_entry(msg->m_databaseid, true); + result = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, &databaseid); + return result; + for (i = 0; i < msg->m_nentries; i++) { + tabentry->blocks_hit = tabmsg->t_counts.t_blocks_hit; + } +``` + + 5. select * from pg_stat_database 的时候通过pg_catalog.pg_stat_get_db_blocks_hit函数从pgStatDBHash中获取到数据库的汇总后的t_blocks_hit统计信息,如下所示: + +``` +pg_stat_get_db_blocks_hit + if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL) + result = 0; + else + result = (int64)(dbentry->n_blocks_hit); // dbentry 存放着的就是步骤4的所有表信息的汇总 +``` + + + +##### 三. 单条sql的缓存命中率统计 + +​ 执行单条sql比如explain (ANALYZE true, buffers true) select * from t1时,可以看到单条sql的缓存命中率,如下所示: + +![image-20240817135320344](./images/image-20240817135320344.png) + +​ 单条sql的缓存命中率实现比较简单,单条sql的缓存命中命中率是存放在u_sess->instr_cxt.pg_buffer_usage->shared_blks_hit中的,explain的时候也是直接从u_sess->instr_cxt.pg_buffer_usage->shared_blks_hit中读取,如下是实现流程: + +1. 初始化planstate的时候,将当前u_sess->instr_cxt的当前缓存统计信息保存到planstate->instr->bufusage_start ,用于后边求差值作为实际的统计信息: + + ``` + InstrStartNode + if (instr->need_bufusage) + instr->bufusage_start = *u_sess->instr_cxt.pg_buffer_usage; + ``` + + + +2. 执行sql,其中根据是否命中缓存,直接修改u_sess->instr_cxt中的统计信息: + +``` +ReadBuffer_common + bufHdr = BufferAlloc(....); + if (found) { + u_sess->instr_cxt.pg_buffer_usage->shared_blks_hit++; + } else { + u_sess->instr_cxt.pg_buffer_usage->shared_blks_read++; + } +``` + + + +3. explain执行计划结束时,再拿到u_sess->instr_cxt统计信息,减去步骤1保存的初始信息,作为此sql的实际命中缓存数据保存到planstate->instrument中,代码流程如下所示: + +``` +InstrStopNode + BufferUsageAccumDiff(&instr->bufusage, u_sess->instr_cxt.pg_buffer_usage, &instr->bufusage_start); + dst->shared_blks_hit += add->shared_blks_hit - sub->shared_blks_hit; // 统计此单条sql的缓存命中率,并且保存到PlanState->instr中 +``` + + + +2. explain从planstate中读取到缓存的命中信息并显示。 + +``` +ExplainNode + show_buffers(es, es->str, planstate->instrument, false, -1, -1, NULL); + usage = &instrument->bufusage + show_buffers_info4 + appendStringInfo(infostr, " hit=%ld", usage->shared_blks_hit); +``` + + + diff --git "a/app/zh/blogs/wangfeihuo/openGauss\347\263\273\347\273\237\351\232\220\350\227\217\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" "b/app/zh/blogs/wangfeihuo/openGauss\347\263\273\347\273\237\351\232\220\350\227\217\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" new file mode 100644 index 0000000000000000000000000000000000000000..fea023a79646c046c649dbac42f9bffae3e13c5e --- /dev/null +++ "b/app/zh/blogs/wangfeihuo/openGauss\347\263\273\347\273\237\351\232\220\350\227\217\345\210\227\345\256\236\347\216\260\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" @@ -0,0 +1,76 @@ +--- +title: 'openGauss系统隐藏列实现原理代码走读' +date: '2024-09-01' +category: 'blog' +tags: ['openGauss系统隐藏列实现原理代码走读'] +archives: '2024-09' +author: 'wangfeihuo' +summary: 'openGauss系统隐藏列实现原理代码走读' +img: '/zh/blogs/wangfeihuo/title/opengauss.png' +times: '9:30' +--- + +##### 一. 前言 + +​ openGauss在建表时,系统会自动插入tableoid,cmax,xmax,cmin,xmin,ctid 六个系统隐藏列,在select*的时候也会自动隐藏这6个系统隐藏列,如下所示: + +![image-20240824223107903](./images/image-20240824223107903.png) + +​ 本文主要走读代码了解openGauss是如何实现系统隐藏列的功能的。 + + + +##### 一. create table时自动往表中插入系统隐藏列 + +​ create table时自动往表中插入系统隐藏列的核心代码入口在heap_create_with_catalog函数中,在往pg_attribute元数据表insert完普通的列后接着insert系统隐藏列的信息,代码流程如下所示: + +``` +heap_create_with_catalog + AddNewAttributeTuples + for (i = 0; i < natts; i++) { + InsertPgAttributeTuple(rel, attr, indstate); // 往PG_ATTRIBUTE系统表中插入表的普通字段 + } + if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_CONTQUERY) { + for (i = 0; i < (int)lengthof(SysAtt); i++) { + InsertPgAttributeTuple(rel, &attStruct, indstate); 往PG_ATTRIBUTE系统表中插入表的隐藏字段,也即xmin,xmax,cmin,cmax等字段,另外,系统隐藏列的AttributeNumber均为在代码中硬编码且均为小于0的负数,这也为区分是某一列是系统隐藏列还是普通列提供了区分的方式 + } + } +``` + + + +##### 二. insert数据时自动往隐藏列insert数据 + +​ 在insert数据的时候,系统的隐藏列的数据也要同时被insert进去,核心代码入口在heap_insert中,insert的场景主要是xmin的值比较重要。 + +``` +heap_insert + TransactionId xid = GetCurrentTransactionId(); + heaptup = heap_prepare_insert(relation, tup, cid, options); + HeapTupleHeaderSetCmin(tup->t_data, cid); // 设置cmin + HeapTupleSetXmax(tup, 0); // 设置xmax值,其实insert场景xmax值一直都是0 + RelationPutHeapTuple(relation, buffer, heaptup, xid); + tuple->t_data->t_choice.t_heap.t_xmin = NormalTransactionIdToShort(pd_xid_base, xid) // 设置xmin,xmin其实就是xid的值,但元组中存储的其实是将xid与xid的差值 + (HeapTupleHeader)item)->t_ctid = tuple->t_self; // 保存t_ctid +``` + + + +##### 三. select *的时候自动忽略隐藏列的数据 + +​ seelct * 的时候自动屏蔽系统隐藏列是扫描pg_attribute获取表的列信息时自动添加过滤条件attnum > 0实现的,主要的实现入口在relation_open中,代码流程如下所示: + +``` +relation_open + RelationIdGetRelation + RelationBuildDesc + RelationBuildTupleDesc + ScanKeyInit(&skey[1], Anum_pg_attribute_attnum, BTGreaterStrategyNumber, F_INT2GT, Int16GetDatum(0)); // 只扫描attnum大于0的列,因为隐藏列的attnum都小于0但真正的物理表的列大于0,因此扫描表的列信息时自动将系统隐藏列忽略 + pg_attribute_scan = systable_beginscan(...skey...) + while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan))) { + memcpy_s(&relation->rd_att->attrs[attp->attnum - 1]...attp...) // 保存列信息到relation中 + } +``` + + + diff --git "a/app/zh/blogs/wangfeihuo/openGauss\350\260\223\350\257\215\344\270\213\346\216\250\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" "b/app/zh/blogs/wangfeihuo/openGauss\350\260\223\350\257\215\344\270\213\346\216\250\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" new file mode 100644 index 0000000000000000000000000000000000000000..ff8b5a431cd0c646d67ed82419299dc33081f02b --- /dev/null +++ "b/app/zh/blogs/wangfeihuo/openGauss\350\260\223\350\257\215\344\270\213\346\216\250\345\216\237\347\220\206\344\273\243\347\240\201\350\265\260\350\257\273.md" @@ -0,0 +1,87 @@ +--- +title: 'openGauss谓词下推原理代码走读' +date: '2024-09-01' +category: 'blog' +tags: ['openGauss谓词下推原理代码走读'] +archives: '2024-09' +author: 'wangfeihuo' +summary: 'openGauss谓词下推原理代码走读' +img: '/zh/blogs/wangfeihuo/title/opengauss.png' +times: '9:30' +--- + +##### 一. 前言 + +​ 谓词下推是每一个SQL引擎必备的功能。本文主要通过走读代码了解openGuass中是如何实现谓词下推能力的。 + +​ 谓词下推即时将过滤条件尽可能往tablescan的节点下推,实现上层算子尽可能少计算的能力,如下所示的谓词id<55就下推到了tablescan节点。 + +![image-20240826001425734](./images/image-20240826001425734.png) + + + +##### 二. 执行计划生成层将谓词信息保存在seqscan node的ps.qual + +​ 实现谓词下推首先需要再执行计划生成节点将谓词过滤条件保存到tablescan节点,openGuass主要靠如下的步骤实现过滤条件保存到SeqScan的: + +1. openGuass首先会在将Join中涉及的物理表提取出来,然后处理where条件中,将where条件谓词的信息保存到对应relation的baserestrictinfo字段中,此步的操作入口在deconstruct_jointree函数中,代码流程如下所示: + +``` +deconstruct_jointree + deconstruct_recurse + if (IsA(jtnode, FromExpr)) + { + foreach (l, f->fromlist) { // 找出from后边的物理表 + sub_joinlist = deconstruct_recurse(...&child_postponed_quals); // 抽取from实体表的eqal条件,保存在child_postponed_quals中 + foreach (l, (List*)f->quals) { // 把每一个谓词条件和其对应的relation绑定起来 + distribute_restrictinfo_to_rels + rel->baserestrictinfo = lappend(rel->baserestrictinfo, restrictinfo); + } + } + } +``` + + + +2. 生成seqscan_plan的时候,如果对应的relation有谓词条件,将谓词条件的信息保存在plan->qual中 + +``` +create_seqscan_plan + scan_clauses = rel->baserestrictinfo; + scan_clauses = extract_actual_clauses(scan_clauses, false); + scan_plan = make_seqscan(tlist, scan_clauses, scan_relid); + plan->qual = qpqual; +``` + + + +3. 在初始化SeqScan Node节点的relation时,再将plan->qual的expr转换成ExprState,保存在node->ps.qual中 + +``` +BeginScanRelation + reset_scan_qual + node->ps.qual = ExecInitExprByRecursion(node->ps.plan->qual) // 把expr转换成ExprState +``` + + + +##### 三. 执行层根据seqscan Node 的ps.qual过滤扫描出来的元组 + +执行层的实现是每扫描扫一个元组,那么使用ps.qual构造出来的谓词条件去比对,满足谓词条件则保留且将元组返回上层,不满足则继续扫描下一个元组,直到满足为止。 + +``` +ExecScan + slot = ExecScanFetch(node, access_mtd, recheck_mtd); // 取到表的元组 + econtext->ecxt_scantuple = slot; // 将元组放置同时保存在econtext->ecxt_scantuple中 + qual = node->ps.qual // + ExecQual(qual, econtext) // 与谓词条件比较,看是否满足 + ExecQualByRecursion + foreach (l, qual) { + ExecEvalExpr(clause, econtext, &isNull, NULL); + ExecEvalOper + ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); + fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], NULL); //将从slot的具体值取出来,作为谓词比较函数(如int4lt)的参数,计算谓词的结果 + } + // 后续如果谓词命中,则返回此元组,否则就继续找下一个元组 +``` +