Postgresql源码(126)TupleStore使用场景与原理分析

相关
《Postgresql源码(125)游标恢复执行的原理分析》
《Postgresql游标使用介绍(cursor)》

总结

  1. 开源PG中使用tuple store来缓存tuple集,默认使用work_mem空间存放,超过可以落盘。
  2. 在PL的returns setof场景 和 loop内commit的场景 会使用tuple store暂存元组。
  3. tuple store的使用方法
    1. 配置dest reveiver为tuple store:CreateDestReceiver / SetTuplestoreDestReceiverParams
    2. 执行:ExecutorRun
    3. 使用tuple store:
      • PL返回的tuplestore拿tuple:FunctionNext → tuplestore_gettupleslot
      • 游标hold后从tuplestore拿tuple:RunFromStore → tuplestore_gettupleslot

TupleStore使用场景一:RETURNS SETOF函数

这个场景的惯用法如下:

// 1. 创建Dest Receiver
treceiver = CreateDestReceiver(DestTuplestore);
// 2 关键函数,配置TupleStore
SetTuplestoreDestReceiverParams(treceiver, tStore, .... ...)
// 3 treceiver传入执行器,调用执行器,结果进入tStore
// 4 清理DestReceiver
// 5 从tStore取出结果使用

用例

create table users(id int, name text, active bool);
insert into users values(1, 'a', true);
insert into users values(2, 'b', false);
insert into users values(3, 'c', true);

CREATE OR REPLACE FUNCTION get_active_users() RETURNS SETOF users AS $$
BEGIN
    RETURN QUERY SELECT * FROM users WHERE active = true;
END;
$$ LANGUAGE plpgsql;

select * from get_active_users();

执行结果

postgres=# select * from get_active_users();
 id | name | active
----+------+--------
  1 | a    | t
  3 | c    | t
(2 rows)

1 exec_stmt_return_query

执行时,会走到exec_stmt_return_query中获取执行结果:
在这里插入图片描述

调用SPI_execute_plan_extended执行后,可以看到tstore中有了两条结果。
在这里插入图片描述
当前堆栈:

(gdb) bt
#0  exec_stmt_return_query (estate=0x7ffc569f6950, stmt=0x17fe708) at pl_exec.c:3655
#1  0x00007fbaa37bc602 in exec_stmts (estate=0x7ffc569f6950, stmts=0x17fec18) at pl_exec.c:2079
#2  0x00007fbaa37bc229 in exec_stmt_block (estate=0x7ffc569f6950, block=0x17fec68) at pl_exec.c:1942
#3  0x00007fbaa37bba2b in exec_toplevel_block (estate=0x7ffc569f6950, block=0x17fec68) at pl_exec.c:1633
#4  0x00007fbaa37b99be in plpgsql_exec_function (func=0x16d72f0, fcinfo=0x17fb7d0, simple_eval_estate=0x0, simple_eval_resowner=0x0, procedure_resowner=0x0, atomic=true) at pl_exec.c:622
#5  0x00007fbaa37d4151 in plpgsql_call_handler (fcinfo=0x17fb7d0) at pl_handler.c:277
#6  0x000000000075c75d in ExecMakeTableFunctionResult (setexpr=0x1763e68, econtext=0x1763d38, argContext=0x17fb6d0, expectedDesc=0x1764138, randomAccess=false) at execSRF.c:234
#7  0x0000000000777ca7 in FunctionNext (node=0x1763b28) at nodeFunctionscan.c:94
#8  0x000000000075df9f in ExecScanFetch (node=0x1763b28, accessMtd=0x777bf5 <FunctionNext>, recheckMtd=0x777fff <FunctionRecheck>) at execScan.c:131
#9  0x000000000075e014 in ExecScan (node=0x1763b28, accessMtd=0x777bf5 <FunctionNext>, recheckMtd=0x777fff <FunctionRecheck>) at execScan.c:180
#10 0x0000000000778049 in ExecFunctionScan (pstate=0x1763b28) at nodeFunctionscan.c:269
#11 0x0000000000759f16 in ExecProcNodeFirst (node=0x1763b28) at execProcnode.c:464
#12 0x000000000074dccf in ExecProcNode (node=0x1763b28) at ../../../src/include/executor/executor.h:274
#13 0x00000000007507cc in ExecutePlan (estate=0x1763900, planstate=0x1763b28, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x17df418, execute_once=true) at execMain.c:1646
#14 0x000000000074e367 in standard_ExecutorRun (queryDesc=0x17dfb90, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:363
#15 0x000000000074e17b in ExecutorRun (queryDesc=0x17dfb90, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:304
#16 0x00000000009e6e62 in PortalRunSelect (portal=0x1787ba0, forward=true, count=0, dest=0x17df418) at pquery.c:924
#17 0x00000000009e6b20 in PortalRun (portal=0x1787ba0, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x17df418, altdest=0x17df418, qc=0x7ffc569f7180) at pquery.c:768
#18 0x00000000009e06a2 in exec_simple_query (query_string=0x16dd080 "select * from get_active_users();") at postgres.c:1274
#19 0x00000000009e4cfb in PostgresMain (dbname=0x16d7910 "postgres", username=0x1715bf8 "mingjie") at postgres.c:4680
#20 0x00000000009dd07f in BackendMain (startup_data=0x7ffc569f748c "", startup_data_len=4) at backend_startup.c:101
#21 0x000000000090be72 in postmaster_child_launch (child_type=B_BACKEND, startup_data=0x7ffc569f748c "", startup_data_len=4, client_sock=0x7ffc569f74b0) at launch_backend.c:265
#22 0x0000000000911702 in BackendStartup (client_sock=0x7ffc569f74b0) at postmaster.c:3593
#23 0x000000000090ebf3 in ServerLoop () at postmaster.c:1674
#24 0x000000000090e5ad in PostmasterMain (argc=1, argv=0x16d58c0) at postmaster.c:1372
#25 0x00000000007d20ac in main (argc=1, argv=0x16d58c0) at main.c:197

2 exec_stmt_return

继续执行到exec_stmt_return,返回值是SET不需要干活。
在这里插入图片描述

3 plpgsql_exec_function

在plpgsql_exec_function最后,处理刚才保存在tstore里面的元组:
在这里插入图片描述
注意这里的estate->rsi指向的是fcinfo->resultinfo,在这里配置的:
在这里插入图片描述
在这里插入图片描述
这样结果数据就可以通过function调用框架返回给SQL层了。

TupleStore使用场景二:游标持久化(TupleStore作为Dest Receiver)

两条路径会使用到游标持久化的功能:

  1. 第一种是创SQL层游标时,使用with hold语法,游标可以跨多个事务存在。
  2. 第二种是循环体内执行commit,这时候循环游标正常会跟着最近一层事务被删掉,但循环还需要继续执行,所以需要hold给循环游标续命。
    在这里插入图片描述

后面分析下第二种场景,循环游标提交hold。

用例


drop procedure tproc1;
CREATE OR REPLACE PROCEDURE tproc1() AS $$
DECLARE
	rec1 record;
BEGIN
for rec1 in select t.a from generate_series(1,10) t(a)
loop
  raise notice '%', rec1.a;
  commit;
  raise notice '%', rec1.a;
  end loop;
END;
$$ LANGUAGE plpgsql;


call tproc1();

结果

postgres=# call tproc1();
NOTICE:  1
NOTICE:  1
NOTICE:  2
NOTICE:  2
NOTICE:  3
NOTICE:  3
NOTICE:  4
NOTICE:  4
NOTICE:  5
NOTICE:  5
NOTICE:  6
NOTICE:  6
NOTICE:  7
NOTICE:  7
NOTICE:  8
NOTICE:  8
NOTICE:  9
NOTICE:  9
NOTICE:  10
NOTICE:  10
CALL

1 HoldPinnedPortals

在这里插入图片描述

2 HoldPortal

在这里插入图片描述

3 HoldPortal→PortalCreateHoldStore

  1. 创建Context,切换过去。
  2. 在新上下文中,执行tuplestore的初始化。
void
PortalCreateHoldStore(Portal portal)
{
	MemoryContext oldcxt;

	portal->holdContext =
		AllocSetContextCreate(TopPortalContext,
							  "PortalHoldContext",
							  ALLOCSET_DEFAULT_SIZES);

	oldcxt = MemoryContextSwitchTo(portal->holdContext);

	portal->holdStore =
		tuplestore_begin_heap(portal->cursorOptions & CURSOR_OPT_SCROLL,
							  true, work_mem);

	MemoryContextSwitchTo(oldcxt);
}

3 HoldPortal→PersistHoldablePortal

void
PersistHoldablePortal(Portal portal)
{
	...

tuplestore需要再自己的上下文中保存一份desc。

	oldcxt = MemoryContextSwitchTo(portal->holdContext);

	portal->tupDesc = CreateTupleDescCopy(portal->tupDesc);

	MemoryContextSwitchTo(oldcxt);

标记portal正在干活:READY → ACTIVE

	MarkPortalActive(portal);
	...
	...
	PG_TRY();
	{
		ScanDirection direction = ForwardScanDirection;

		ActivePortal = portal;
		if (portal->resowner)
			CurrentResourceOwner = portal->resowner;
		PortalContext = portal->portalContext;

		MemoryContextSwitchTo(PortalContext);

		PushActiveSnapshot(queryDesc->snapshot);
		
		...

创建tuple store的dest receiver并开始执行:

		queryDesc->dest = CreateDestReceiver(DestTuplestore);
		SetTuplestoreDestReceiverParams(queryDesc->dest,
										portal->holdStore,
										portal->holdContext,
										true,
										NULL,
										NULL);

注意这里ExecutorRun的第三个参数是0,表示拿完为止。和游标fetch是有区别的,fetch一次这里会传入1,只拿一条。
在这里插入图片描述

参考这篇:《Postgresql源码(125)游标恢复执行的原理分析》

继续分析:

		ExecutorRun(queryDesc, direction, 0, false);

		queryDesc->dest->rDestroy(queryDesc->dest);
		queryDesc->dest = NULL;

		portal->queryDesc = NULL;	/* prevent double shutdown */
		ExecutorFinish(queryDesc);
		ExecutorEnd(queryDesc);
		FreeQueryDesc(queryDesc);
		MemoryContextSwitchTo(portal->holdContext);

执行后tuples=9,之前已经查出来一条,现在把剩下的9条都拿到了。
(gdb) p *portal->holdStore $6 = {status = TSS_INMEM, eflags = 4, backward = false, interXact = true, truncated = false, availMem = 8371768, allowedMem = 8388608, tuples = 9, myfile = 0x0, context = 0x1855da0, resowner = 0x1717530, copytup = 0xc0051f <copytup_heap>, writetup = 0xc0056a <writetup_heap>, readtup = 0xc00635 <readtup_heap>, memtuples = 0x1821e68, memtupdeleted = 0, memtupcount = 9, memtupsize = 2048, growmemtuples = true, readptrs = 0x1855fb0, activeptr = 0, readptrcount = 1, readptrsize = 8, writepos_file = 0, writepos_offset = 0}

	...
	...

	}
	...
	PG_END_TRY();

	MemoryContextSwitchTo(oldcxt);

portal用完了把状态改回来:

	/* Mark portal not active */
	portal->status = PORTAL_READY;
	...
	...
}

至此,游标内容已经全部进入tuple store。

SPI_cursor_fetch

commit后,在循环中会继续fetch游标,这里一个较大的区别时,不在执行portal拿结果了,从tuple store拿一行即可:
在这里插入图片描述
这里使用tuplestore_gettupleslot从tuple里面拿数据:

static uint64
RunFromStore(Portal portal, ScanDirection direction, uint64 count,
			 DestReceiver *dest)
{
	...
	...
	ok = tuplestore_gettupleslot(portal->holdStore, forward, false, slot);
	...
	...
	return current_tuple_count;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/553905.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于51单片机的步进电机调速系统设计

基于51单片机的步进电机调速系统 &#xff08;仿真&#xff0b;程序&#xff0b;原理图&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.按键可以控制电机正、反转&#xff0c;加、减速&#xff0c;停止&#xff1b; 2.一位7段数码管实时显示档位&#xf…

6个免费的伪原创工具,轻松生成原创文章

如今&#xff0c;内容创作已经成为许多人关注的焦点。然而&#xff0c;随之而来的是创作压力和时间成本的增加。为了解决这些问题&#xff0c;越来越多的人开始寻找一些伪原创工具来帮助他们生成原创文章&#xff0c;其中免费的伪原创工具成为了热门选择之一。这些免费的伪原创…

建都寿春的袁术兴亡史

三国(220年-280年)是中国历史上位于汉朝之后&#xff0c;晋朝之前的一段历史时期。这一个时期&#xff0c;先后出现了曹魏、蜀汉、东吴三个主要政权。袁术的地盘很小&#xff0c;为了在三国时期能够立足&#xff1f; 事实上&#xff0c;袁术巅峰时期的地盘并不小&#xff0c;而…

类和对象中-运算符重载

在C中&#xff0c;有些成员函数如果我们不去显示定义&#xff0c;编译器会自动生成 会自动生成的特殊函数&#xff1a; 他们不能定义为全局函数&#xff0c;必须是类成员员函数&#xff08;特别是拷贝赋值重载&#xff09; 下面介绍默认生成函数的作用&#xff0c;特点 构造 …

工作流JBPM流程图说明

文章目录 5☃️ 相关概念6 ☃️流程图说明6.0 ❄️❄️快速上手6.1 ❄️❄️活动Activity / 节点Node6.1.1 start 开始活动6.1.2 end 结束活动6.1.3 task 任务活动6.1.4 decision 判断活动6.1.5 fork/join 分支/聚合活动 6.2 ❄️❄️流转 Transition / 连线 &#xff08;单向箭…

Python相关性分析

分析连续变量之间线性相关程度的强弱&#xff0c;并用适当的统计指标表示出来的过程称为相关分析。 可以直接绘制散点图&#xff0c;或者绘制散点图矩阵&#xff0c;或者计算相关系数来进行相关分析。 相关系数的计算如下所示&#xff1a; 示例数据&#xff1a; 计算百合酱蒸…

云服务器安装Mysql、MariaDB、Redis、tomcat、nginx

前置工作 进入根目录 cd / 都在/usr/local/src文件夹&#xff09; 上传压缩包 rz 压缩包 Mysql 1.下载并安装MySQL官方的 Yum Repository wget http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm rpm -ivh mysql-community-release-el7-5.noarch.rpm yum…

lua 光速入门

文章目录 安装注释字符串变量逻辑运算条件判断循环函数Table (表)常用全局函数模块化 首先明确 lua 和 js Python一样是动态解释性语言&#xff0c;需要解释器执行。并且不同于 Python 的强类型与 js 的弱类型&#xff0c;它有点居中&#xff0c;倾向于强类型。 安装 下载解释…

美易官方:人民币国际支付占比升至近5%

随着全球金融市场的不断发展和数字化进程的加速&#xff0c;人民币的国际支付地位逐渐提升&#xff0c;成为备受瞩目的焦点。最近的数据显示&#xff0c;人民币在国际支付中的占比已经升至近5%&#xff0c;自11月以来已成为第四大交易货币。这一变化不仅反映了中国经济的崛起和…

Python 密码学实用指南(全)

原文&#xff1a;zh.annas-archive.org/md5/fe5e9f4d664790ea92fb33d78ca9108d 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 前言 密码学在保护关键系统和敏感信息方面有着悠久而重要的历史。本书将向您展示如何使用 Python 加密、评估、比较和攻击数据。总的来说&…

MDK stm32怎么生成bin文件

第一种 D:\Keil_v5\ARM\ac5.6\bin\fromelf.exe --bin -o ../../Output/atk_f407.bin ../../Output/atk_f407.axf 空格解析 D:\Keil_v5\ARM\ac5.6\bin\fromelf.exe一个空格--bin一个空格-o两个空格../../Output/atk_f407.bin ../../Output/atk_f407.axf &#xff08;注意后…

Rose中UML类图详解及画法

一、UML简介 UML&#xff08;Unified Modeling Language&#xff09;是一种用于软件系统建模的标准化工具&#xff0c;它提供了一套统一的符号和语法&#xff0c;用于描述、设计、构建和交流软件系统的结构和行为。UML广泛应用于软件开发领域&#xff0c;被视为一种通用的建模语…

【C语言】qsort()函数排序及其模拟实现,万物皆可排!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 目录 1. 函数介绍 2. qsort举例排列整型变量 3. qsort举例排列结构型变量 3.1 按名字排序 3.1.1 srtcmp函数 3.2 按年龄排序 4. qsort函数模拟实现(采用冒泡的…

宝塔要注意的问题

数据库创建访问权限要全部人 反向代理1 打包dist,并不会有反向代理&#xff0c;所以宝塔里面要配置 反向代理2 这种去掉/api为/&#xff0c;上面的并没有去掉 rewrite ^/api/(.*)$ /$1 break;

mysql数据库表的数据显示到前端tableView

首先我们在ui视图设计中引入TableView, 定义一个model QSqlQueryModel *modelnew QSqlQueryModel(ui->tableView);model->setQuery(query);//将查询结果绑定到模型上ui->tableView->setModel(model); 将tableView内容设置成model然后就可以出现数据库的数据。示…

uni.uploadFile上传图片后台接收不到数据

今天遇到一个很奇怪的问题&#xff0c;通过使用uni.uploadFile上传文件时后端接收不到文件&#xff0c;查过很多资料&#xff0c;原来是自定义了header的Content-Type问题。取消即可&#xff0c;另把自定义文件上传的代码贴出来。 分析&#xff1a;当我们加上请求头的时候 不…

一.NODE MCU(ESP8285,ESP8286)开发环境搭建

一.序言: 1.esp8285长什么样? 2.esp8285是什么,能做什么? 通过上面图片,看到上面的芯片,是带有多个阵脚的单片机。实际上,看着该芯片很小,但是却具有完整的wifi无线蓝牙功能,它本身可以运行一个极简的linux小系统,并且该极简的小linux系统具备无线蓝牙功能。。它同…

了解光纤的最大损耗

在电信和数据传输领域&#xff0c;保持最佳的网络性能和可靠性至关重要。 影响网络完整性的关键因素之一是光纤中的信号丢失。信号损耗&#xff0c;也称为衰减损耗或光纤衰减&#xff0c;测量光缆输入和输出之间的光损耗量。本文将深入探讨光纤中的主要损耗&#xff0c;并指导您…

压缩感知的概述梳理(2)

参考文献 An efficient double-image encryption and hiding algorithm using a newly designed chaotic system and parallel compressive sensing 文献内容 梳理结果 列表形式 并行压缩感知核心元素 信号 x 和 s 信号 x: 稀疏信号信号 s: 非稀疏自然信号&#xff0c;在频…

数据库的创建

数据库分类 通过查看对象资源管理器来区分数据库类型 数据库物理文件的组成 : 数据库文件 日志文件 创建一个主数据文件和一个日志文件
最新文章