Postgresql源码(125)游标恢复执行的原理分析

问题

为什么每次fetch游标能从上一次的位置继续?后面用一个简单用例分析原理。

【速查】
恢复扫描需要知道当前页面、上一次扫描到的偏移位置、当前页面一共有几条:

  1. 当前页面:HeapScanDesc结构中记录了扫到的页面(scan->rs_cblock
  2. 上一次扫描到的偏移位置:scan->rs_cindex,注意rs_cindex是每个页面内的可见元组,从0开始算,每个页面都会从0遍历到scan->rs_ntuples为止。
  3. 当前页面一共有几条:scan->rs_ntuples记录了当前页面有几个vis元组,在heapgetpage函数中计算。

场景一:open curs1 FOR SELECT ...

drop table tf1;
create table tf1(c1 int, c2 int,  c3 varchar(32), c4 varchar(32), c5 int);
insert into tf1 values(1,1000, 'China','Dalian', 23000);
insert into tf1 values(2,4000, 'Janpan', 'Tokio', 45000);
insert into tf1 values(3,1500, 'China', 'Xian', 25000);
insert into tf1 values(4,300, 'China', 'Changsha', 24000);
insert into tf1 values(5,400,'USA','New York', 35000);
insert into tf1 values(6,5000, 'USA', 'Bostom', 15000);drop procedure tproc1;
CREATE OR REPLACE PROCEDURE tproc1() AS $$
DECLAREcurs1 refcursor;  y tf1%ROWTYPE;                     
BEGINopen curs1 FOR SELECT * FROM tf1 WHERE c1 > 3;fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;
END;
$$ LANGUAGE plpgsql;call tproc1();

1 OPEN

exec_stmt_open中的执行结构

(gdb) p *stmt
$3 = {cmd_type = PLPGSQL_STMT_OPEN,lineno = 6,stmtid = 1,curvar = 1,cursor_options = 256,argquery = 0x0,query = 0x1824390,dynquery = 0x0,params = 0x0
}
(gdb) p *stmt->query
$5 = {query = 0x1824698 "SELECT * FROM tf1 WHERE c1 > 3",parseMode = RAW_PARSE_DEFAULT,plan = 0x0,paramnos = 0x0,func = 0x0,ns = 0x1824570,expr_simple_expr = 0x0,expr_simple_type = 0,expr_simple_typmod = 0,expr_simple_mutable = false,target_param = -1,expr_rw_param = 0x0,expr_simple_plansource = 0x0,expr_simple_plan = 0x0,expr_simple_plan_lxid = 0,expr_simple_state = 0x0,expr_simple_in_use = false,expr_simple_lxid = 0
}

第一步:exec_prepare_plan

exec_stmt_openexec_prepare_planSPI_prepare_extended_SPI_prepare_planraw_parserCreateCachedPlanpg_analyze_and_rewrite_withcbCompleteCachedPlanSPI_keepplanexec_simple_check_plan

结果保存在stmt->query->plan

第二步:SPI_cursor_open_with_paramlist

exec_stmt_open-- 有参数时会构造ParamListInfo返回-- 这里没参数,返回NULLsetup_param_listSPI_cursor_open_with_paramlistSPI_cursor_open_internalCreateNewPortal-- 没ParamListInfo一定走generic planGetCachedPlanPortalDefineQuery-- 拿快照CommandCounterIncrementGetTransactionSnapshot-- 主要是为了执行InitNodePortalStartCreateQueryDescExecutorStartstandard_ExecutorStartCreateExecutorStateInitPlanExecInitRangeTableExecInitNodeExecGetResultType

2 FETCH

第一步:找到portal

curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
curname = TextDatumGetCString(curvar->value);
portal = SPI_cursor_find(curname);

第二步:计算fetch几个?

if (stmt->expr)how_many = exec_eval_integer(estate, stmt->expr, &isnull);

第三步:FETCH

SPI_scroll_cursor_fetch(portal, FETCH_FORWARD, 1)_SPI_cursor_operation(.., CreateDestReceiver(DestSPI))PortalRunFetch(portal, FETCH_FORWARD, 1, dest=<spi_printtupDR>)MarkPortalActiveDoPortalRunFetchPortalRunSelect(portal, forward=true, count=1, dest=<spi_printtupDR>)PushActiveSnapshotExecutorRun(queryDesc, direction=ForwardScanDirection, count=1, execute_once=false)-- 配置接受者,现在是SPI-- SPI会存到_SPI_current->tuptables中dlist-- 每个元素是 tuptable,tuptable->vals存放HeapTupledest->rStartupspi_dest_startup-- 这里入参有一个numberTuples=1表示只执行一条ExecutePlanfor (;;)-- 这里只执行一次,那么多次fetch是怎么能继续上次执行的?ExecProcNode-- 这里只拿一条,拿到就退if (numberTuples && numberTuples == current_tuple_count)break;PopActiveSnapshot

ExecProcNode展开:执行一次

ExecProcNodeExecProcNodeFirstExecSeqScanExecScanfor (;;)ExecScanFetchSeqNext-- 第一次进来创建scandescif (scandesc == NULL)scandesc = table_beginscan(...)-- 开始扫描table_scan_getnextslot(scandesc, direction, slot)heap_getnextslotheapgettup_pagemode()

heapgettup_pagemode执行第一次:
在这里插入图片描述
在这里插入图片描述

heapgettup_pagemode执行第N次:
在这里插入图片描述

所以为什么每次游标fetch都能继续上次的值:

  1. HeapScanDesc结构中记录了扫到的页面(scan->rs_cblock)、页面中的位置(scan->rs_cindex),注意rs_cindex是每个页面内的可见元组需要,从0开始算,每个页面都会从0遍历到scan->rs_ntuples为止。
  2. scan->rs_ntuples记录了当前页面有几个vis元组,在heapgetpage函数中计算。

场景二:open curs1 FOR EXECUTE ...

drop table tf1;
create table tf1(c1 int, c2 int,  c3 varchar(32), c4 varchar(32), c5 int);
insert into tf1 values(1,1000, 'China','Dalian', 23000);
insert into tf1 values(2,4000, 'Janpan', 'Tokio', 45000);
insert into tf1 values(3,1500, 'China', 'Xian', 25000);
insert into tf1 values(4,300, 'China', 'Changsha', 24000);
insert into tf1 values(5,400,'USA','New York', 35000);
insert into tf1 values(6,5000, 'USA', 'Bostom', 15000);CREATE OR REPLACE PROCEDURE tproc1() AS $$
DECLAREcurs1 refcursor;  y tf1%ROWTYPE;                     
BEGINopen curs1 FOR EXECUTE 'SELECT * FROM tf1 WHERE c1 > $1' using 3;fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;fetch curs1 into y; RAISE NOTICE 'curs1 : %', y.c3;
END;
$$ LANGUAGE plpgsql;call tproc1();

OPEN区别

不在执行exec_prepare_plan直接执行exec_dynquery_with_params:

exec_stmt_openportal = exec_dynquery_with_params-- 第一步:把表达式计算出来 "SELECT * FROM tf1 WHERE c1 > $1"-- 因为有可能使用表达式,比如"select * " || "from " || "tf1" exec_eval_exprSPI_cursor_parse_open_SPI_prepare_planSPI_cursor_open_internalCreateNewPortalGetCachedPlan-- 注意这里会把plan删了,portal define的时候是用的copy的,计划没有缓存。ReleaseCachedPlan(cplan, NULL);stmt_list = copyObject(stmt_list);PortalDefineQuery(stmt_list)PortalStart

FETCH区别

exec_stmt_fetchSPI_scroll_cursor_fetch_SPI_cursor_operationPortalRunFetch

这里的portal没有plan

p *portal
$50 = {name = 0x178b550 "<unnamed portal 10>", prepStmtName = 0x0, portalContext = 0x1841b00, resowner = 0x172efe8, cleanup = 0x6cb0d2 <PortalCleanup>, createSubid = 1, activeSubid = 1, createLevel = 1, sourceText = 0x1841c00 "SELECT * FROM tf1 WHERE c1 > $1", commandTag = CMDTAG_SELECT, qc = {commandTag = CMDTAG_SELECT, nprocessed = 0},stmts = 0x1841c30,  <<<<<<<<< ------- 拷贝的计划在这里,运行时用这里的计划cplan = 0x0,      <<<<<<<<<<< ------- 注意这里没plan,已经清理了portalParams = 0x18531b8, queryEnv = 0x0, strategy = PORTAL_ONE_SELECT, cursorOptions = 258, run_once = false, status = PORTAL_READY,portalPinned = false, autoHeld = false, queryDesc = 0x1853248, tupDesc = 0x1849288, formats = 0x0, portalSnapshot = 0x0,  holdStore = 0x0, holdContext = 0x0, holdSnapshot = 0x0,atStart = true, atEnd = false, portalPos = 0, creation_time = 766486974937570, visible = true}

继续执行

PortalRunFetchDoPortalRunFetchPortalRunSelectExecutorRunfor (;;)ExecProcNode

后续流程相同。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apache Paimon 流式湖仓介绍说明

文章目录 前言选择 Paimon 的原因Apache Paimon 功能一致性保证Paimon 表类型数据湖写入标签和时间线回溯捕获变更数据写入数据湖LSM 和分层文件重用流处理案例使用 Paimon 作为消息队列 前言 Apache Flink 自诞生以来经历了重大演变&#xff0c;如今&#xff0c;它不仅充当批…

毕设选51还是stm32?51太简单?

如果你更倾向于挑战和深入学习&#xff0c;STM32可能是更好的选择。如果你希望更专注于底层硬件原理&#xff0c;51可能更适合。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff…

阿里云迁移到AWS云,九河云保姆级教程

随着云计算技术的不断发展,越来越多的企业开始将传统的IT基础设施迁移到云平台上,以获得更高的灵活性、可扩展性和成本效益。在众多云服务提供商中,阿里云和AWS都是备受青睐的选择。本文将探讨如何将阿里云上的资源顺利迁移到AWS云平台,并针对性地进行优化。我们九河云&#xf…

[图解]DDD领域驱动设计伪创新-聚合根06

0 00:00:00,740 --> 00:00:02,200 那刚才讲了 1 00:00:02,480 --> 00:00:04,211 Evans这个隐喻 2 00:00:04,211 --> 00:00:06,520 实际上背后是把集合 3 00:00:06,800 --> 00:00:08,560 当成了聚合 4 00:00:10,580 --> 00:00:14,350 那为什么有这样的一个隐…

OpenHarmony实战开发-如何使用AKI轻松实现跨语言调用。

介绍 针对JS与C/C跨语言访问场景&#xff0c;NAPI使用比较繁琐。而AKI提供了极简语法糖使用方式&#xff0c;一行代码完成JS与C/C的无障碍跨语言互调&#xff0c;使用方便。本示例将介绍使用AKI编写C跨线程调用JS函数场景。通过调用C全局函数&#xff0c;创建子线程来调用JS函…

GIS 数据格式转换

1、在线工具 mapshaper 2、数据上传 3、数据格式转换 导入数据可导出为多种格式&#xff1a;Shapefile、Json、GeoJson、CSV、TopJSON、KML、SVG

APP广告变现项目

APP广告变现项目 很多人觉得不可能&#xff0c;这是肯定存在的&#xff0c;不是现在才有的一个项目&#xff0c;这个项目的原理是怎么样呢&#xff0c;就是通过某些特定的app&#xff0c;然后看完广告就有收益&#xff0c;基本单次的观看单价都是在几毛到1块之间。 养机养好的…

阿里云服务器带宽多少钱?公网带宽收费标准全解析

阿里云服务器的公网带宽计费模式分为“按固定带宽”和“按使用流量”&#xff0c;有什么区别&#xff1f;按固定带宽是指直接购买多少M带宽&#xff0c;比如1M、5M、10M、100M等&#xff0c;阿里云直接分配用户所购买的带宽值&#xff0c;根据带宽大小先付费再使用&#xff1b;…

一套3种风格经典的wordpress免费主题模板

wordpress免费企业主题 https://www.wpniu.com/themes/39.html 免费wordpress企业模板 https://www.wpniu.com/themes/43.html 免费wordpress企业主题 https://www.wpniu.com/themes/44.html

波奇学Linux:ip协议

ip报头是c语言的结构体 报头和有效载荷如何分离&#xff1f; 固定长度四位首部长度 4位版本号就是IPV4 8位服务类型&#xff1a;4位TOS位段和位保留字段 4位TOS分别表示&#xff1a;最小延时&#xff0c;最大吞吐量&#xff0c;最高可靠性&#xff0c;最小成本 给路由器提…

【JAVA基础篇教学】第十六篇:Java连接和操作MySQL数据库

博主打算从0-1讲解下java基础教学&#xff0c;今天教学第十六篇&#xff1a;Java连接和操作MySQL数据库。 我将提供一个简单的示例代码&#xff0c;涵盖数据库连接、查询、插入和更新等操作。 一、下载MySQL驱动包 1.下载地址&#xff1a;MySQL :: Download Connector/J 2.解…

Navicat for MySQL 使用基础与 SQL 语言的DDL

一、目的&#xff1a; Navicat for MySQL 是一套专为 MySQL 设计的高性能数据库管理及开发 工具。它可以用于任何版本 3.21 或以上的 MySQL 数据库服务器&#xff0c;并支持大 部份 MySQL 最新版本的功能&#xff0c;包括触发器、存储过程、函数、事件、视图、 管理用户等。…

VMware配置CentOS 7 并实现ssh连接

Vmware 17下载地址 ***永久许可证&#xff1a;***5Y012-8HL8P-0J8U0-032Q6-93KKF CentOS 7 下载地址 一、配置CentOS 如下 创建新的虚拟机&#xff0c;选择典型&#xff0c;点击下一步 选择上述下载镜像存储位置&#xff0c;选择镜像&#xff0c;点击下一步 3.填写相关信息…

微信小程序wx.getLocation 真机调试不出现隐私弹窗

在小程序的开发过程中&#xff0c;首页中包含要获取用户地理位置的功能&#xff0c;所以在这里的onLoad&#xff08;&#xff09;中调用了wx.getLocation()&#xff0c;模拟调试时一切正常&#xff0c;但到了真机环境中就隐私框就不再弹出&#xff0c;并且出现了报错&#xff0…

智能生活新体验:小米香薰加湿器技术解码

在现代家居生活中&#xff0c;科技与舒适性日益交织&#xff0c;智能家居产品成为提升生活品质的重要工具。小米香薰加湿器作为一款集科技与生活美学于一体的产品&#xff0c;其独特的设计和多功能性受到了广泛欢迎。今天&#xff0c;我们就来详细拆解这款融合了科技与香薰元素…

Sony Camera Remote SDK在Windows上的使用

Sony官方提供了相机遥控软件开发包&#xff0c;允许用户自行开发应用软件&#xff0c;实现对相机的远程控制&#xff0c;包括拍摄、监看和文件传输等。截至目前最新的版本是2024.4.12发布的1.12.00版本&#xff0c;下载链接如下&#xff1a;Camera Remote SDK | LICENSE AGREEM…

初学python记录:力扣706. 设计哈希映射

题目&#xff1a; 不使用任何内建的哈希表库设计一个哈希映射&#xff08;HashMap&#xff09;。 实现 MyHashMap 类&#xff1a; MyHashMap() 用空映射初始化对象void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中&#x…

SpringCloud之LoadBalancer自定义负载均衡算法,基于nacos权重

LoadBalancer基于Nacos权重自定义负载算法 ReactorLoadBalancer接口&#xff0c;实现自定义负载算法需要实现该接口&#xff0c;并实现choose逻辑&#xff0c;选取对应的节点 public interface ReactorLoadBalancer<T> extends ReactiveLoadBalancer<T> {Mono<…

VMware Workstation部署最新版OpenWrt 23.05.3

正文共&#xff1a;1456 字 51 图&#xff0c;预估阅读时间&#xff1a;2 分钟 我们之前介绍了如何在VMware Workstation上安装OpenWrt&#xff08;软路由是啥&#xff1f;OpenWrt又是啥&#xff1f;长啥样&#xff1f;在VMware装一个瞅瞅&#xff09;&#xff0c;也介绍了如何…

【计算机毕业设计】物流管理系统设计与实现——后附源码

&#x1f389;**欢迎来到琛哥的技术世界&#xff01;**&#x1f389; &#x1f4d8; 博主小档案&#xff1a; 琛哥&#xff0c;一名来自世界500强的资深程序猿&#xff0c;毕业于国内知名985高校。 &#x1f527; 技术专长&#xff1a; 琛哥在深度学习任务中展现出卓越的能力&a…