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 $$
DECLARE
    curs1 refcursor;  
    y tf1%ROWTYPE;                     
BEGIN
    open 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_open
	exec_prepare_plan
		SPI_prepare_extended
			_SPI_prepare_plan
				raw_parser
				CreateCachedPlan
				pg_analyze_and_rewrite_withcb
				CompleteCachedPlan
		SPI_keepplan
		exec_simple_check_plan

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

第二步:SPI_cursor_open_with_paramlist

exec_stmt_open
	-- 有参数时会构造ParamListInfo返回
	-- 这里没参数,返回NULL
	setup_param_list
	SPI_cursor_open_with_paramlist
		SPI_cursor_open_internal
			CreateNewPortal
			-- 没ParamListInfo一定走generic plan
			GetCachedPlan
			PortalDefineQuery
			
			-- 拿快照
			CommandCounterIncrement
			GetTransactionSnapshot
			
			-- 主要是为了执行InitNode
			PortalStart
				CreateQueryDesc
				ExecutorStart
					standard_ExecutorStart
						CreateExecutorState
						InitPlan
							ExecInitRangeTable
							ExecInitNode
							ExecGetResultType

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>)
			MarkPortalActive
			DoPortalRunFetch
				PortalRunSelect(portal, forward=true, count=1, dest=<spi_printtupDR>)
					PushActiveSnapshot
					ExecutorRun(queryDesc, direction=ForwardScanDirection, count=1, execute_once=false)
						-- 配置接受者,现在是SPI
						-- SPI会存到_SPI_current->tuptables中dlist
						-- 每个元素是 tuptable,tuptable->vals存放HeapTuple
						dest->rStartup
							spi_dest_startup
						-- 这里入参有一个numberTuples=1表示只执行一条
						ExecutePlan
							for (;;)
								-- 这里只执行一次,那么多次fetch是怎么能继续上次执行的?
								ExecProcNode
								-- 这里只拿一条,拿到就退
								if (numberTuples && numberTuples == current_tuple_count)
									break;
					PopActiveSnapshot

ExecProcNode展开:执行一次

ExecProcNode
	ExecProcNodeFirst
		ExecSeqScan
			ExecScan
				for (;;)
					ExecScanFetch
						SeqNext
							-- 第一次进来创建scandesc
							if (scandesc == NULL)
								scandesc = table_beginscan(...)
							-- 开始扫描
							table_scan_getnextslot(scandesc, direction, slot)
								heap_getnextslot
									heapgettup_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 $$
DECLARE
    curs1 refcursor;  
    y tf1%ROWTYPE;                     
BEGIN
    open 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_open
	portal = exec_dynquery_with_params
		-- 第一步:把表达式计算出来 "SELECT * FROM tf1 WHERE c1 > $1"
		-- 因为有可能使用表达式,比如"select * " || "from " || "tf1" 
		exec_eval_expr
		SPI_cursor_parse_open
			_SPI_prepare_plan
			SPI_cursor_open_internal
				CreateNewPortal
				GetCachedPlan
				-- 注意这里会把plan删了,portal define的时候是用的copy的,计划没有缓存。
				ReleaseCachedPlan(cplan, NULL);
				stmt_list = copyObject(stmt_list);
				PortalDefineQuery(stmt_list)
				PortalStart

FETCH区别

exec_stmt_fetch
	SPI_scroll_cursor_fetch
		_SPI_cursor_operation
			PortalRunFetch

这里的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}

继续执行

PortalRunFetch
	DoPortalRunFetch
		PortalRunSelect
			ExecutorRun
				for (;;)
					ExecProcNode

后续流程相同。

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

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

相关文章

计算机网络:数据链路层 - CSMA/CA协议

计算机网络&#xff1a;数据链路层 - CSMA/CA协议 CSMA/CA概述帧间间隔工作原理退避算法虚拟载波监听 CSMA/CA概述 讲解CSMA/CA之前&#xff0c;我们回顾一下CSMA/CD的三个特性&#xff1a; 多址接入MA&#xff1a;多个主机连接在一条总线上&#xff0c;竞争使用总线 载波监听…

2024年主流的java混淆工具有哪些

2024年&#xff0c;主流的Java混淆工具可能会包括&#xff1a; ProGuard&#xff1a;ProGuard 是一个免费的开源 Java 混淆工具&#xff0c;可用于压缩、优化和混淆 Java 字节码。它是Android开发者的首选混淆工具之一&#xff0c;并且在Java应用程序中也得到了广泛应用。 Dex…

Stable Diffusion超详细教程!从0-1入门到进阶

一、本地部署 Stable Diffusion 前言 目前市面上比较权威&#xff0c;并能用于工作中的AI绘画软件其实就两款。一个叫Midjourney&#xff08;简称MJ&#xff09;&#xff0c;另一个叫Stable-Diffusion&#xff08;简称SD&#xff09;。MJ需要付费使用&#xff0c;而SD开源免费…

【洛谷 P8802】[蓝桥杯 2022 国 B] 出差 题解(带权无向图+单源最短路+Dijkstra算法+链式前向星+最小堆)

[蓝桥杯 2022 国 B] 出差 题目描述 A \mathrm{A} A 国有 N N N 个城市&#xff0c;编号为 1 … N 1 \ldots N 1…N 小明是编号为 1 1 1 的城市中一家公司的员工&#xff0c;今天突然接到了上级通知需要去编号为 N N N 的城市出差。 由于疫情原因&#xff0c;很多直达的交…

【GEE实践应用】统计遥感数据像元的观测值数量以及良好观测值数量

下面我们以贵州省毕节市2016年8月1号至2018年7月31号两年间像元的观测值数量以及良好的观测值数量为例&#xff0c;统计结果以图像形式进行输出&#xff0c;如图1所示&#xff1a; // 1. 定义研究区域 var studyArea table;// 获取 Landsat 和 Sentinel-2 数据集 var landsat…

第九届少儿模特明星盛典 全球赛首席体验官『韩嘉滢』精彩回顾

2024年1月30日-2月1日&#xff0c;魔都上海迎来了龙年第一场“少儿形体行业美育春晚”&#xff01;由IPA模特委员会主办的第九届少儿模特明星盛典全球总决赛圆满收官&#xff01;近2000名少儿模特选手从五湖四海而来&#xff0c;决战寒假这场高水准&#xff0c;高人气&#xff…

前端上传照片压缩 (适合 vue vant组件的)

为什么要这样做&#xff1f; &#xff08;减小服务器压力 提升用户体验上传照片和加载照片会变快&#xff09; 最近有一个需求&#xff0c;通过手机拍照后上传图片到服务器&#xff0c;大家应该都知道&#xff0c;现在的手机像素实在是太高了&#xff0c;随便拍一张都是10M以上…

物联网的核心价值是什么?——青创智通

工业物联网解决方案-工业IOT-青创智通 物联网&#xff0c;这个词汇在当今的科技领域已经变得耳熟能详。但当我们深入探索物联网的核心价值时&#xff0c;我们会发现它远不止是一个简单的技术概念&#xff0c;而是一种能够彻底改变我们生活方式和工作方式的革命性力量。 物联网…

Django之rest_framework(三)

一、GenericAPIView的使用 rest_framework.generics.GenericAPIView 继承自APIVIew,主要增加了操作序列化器和数据库查询的方法,作用是为下面Mixin扩展类的执行提供方法支持。通常在使用时,可搭配一个或多个Mixin扩展类 1.1、属性 serializer_class 指明视图使用的序列化器…

JVM之JVM栈的详细解析

Java 栈 Java 虚拟机栈&#xff1a;Java Virtual Machine Stacks&#xff0c;每个线程运行时所需要的内存 每个方法被执行时&#xff0c;都会在虚拟机栈中创建一个栈帧 stack frame&#xff08;一个方法一个栈帧&#xff09; Java 虚拟机规范允许 Java 栈的大小是动态的或者是…

npm配置阿里镜像库

1、配置阿里云镜像源 #查看当前使用的镜像地址命令 npm config get registry#设置阿里镜像源 npm config set registry http://registry.npmmirror.com 这里要注意下&#xff0c;之前的镜像源地址 https://registry.npm.taobao.org/ 已经不能用了&#xff0c;这里要更改为新…

Grok-1.5 Vision 预览 将数字世界与物理世界连接起来,首款多模态模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

android 创建module

文章目的&#xff1a; 快速创建module并使用 创建步骤&#xff1a; 1 创建module 2 修改module下的build.gradle文件 3 修改清单文件中MainActivity属性&#xff0c;否则APP会因为有多个启动界面而崩溃 4 在主项目build.gradle引用该object Module 至此&#xff0c;可在APP中…

golang 迷宫回溯算法(递归)

// Author sunwenbo // 2024/4/14 20:13 package mainimport "fmt"// 编程一个函数&#xff0c;完成老鼠找出路 // myMap *[8][7]int 地图&#xff0c;保证是同一个地图&#xff0c;因此是引用类型 // i,j表示对地图的哪个点进行测试 func SetWay(myMap *[8][7]int, …

【ARM 裸机】汇编 led 驱动之烧写 bin 文件

1、烧写概念 bin 文件烧写到哪里呢&#xff1f;使用 STM32 的时候烧写到内部 FLASH&#xff0c;6ULL 没有内部 FLASH&#xff0c;是不是就不能烧写呢&#xff1f;不&#xff0c;6ULL 支持 SD卡、EMMC、NAND FLASH、NOR FLASH 等方式启动&#xff0c;在裸机学习的工程中&#x…

参会记录|全国多媒体取证暨第三届多媒体智能安全学术研讨会(MAS‘2024)

前言&#xff1a;2024年4月13日上午&#xff0c;我与实验室的诸位伙伴共聚江西南昌的玉泉岛大酒店&#xff0c;参加了为期一天半的全国多媒体取证暨第三届多媒体智能安全学术研讨会&#xff08;MAS’2024&#xff09;。本届学术研讨会由江西省计算机学会、江西省数字经济学会主…

【学习笔记十七】波次管理、自动波次和WOCR介绍及配置

一、手工维护波次 波次是控制仓库活动(如拣配)的仓库请求项目(通常是出库交货订单项目)的分组。这些分组随后在后续流程中一起处理,例如,将分配到波次的所有仓库请求项目传输到仓库任务创建。 注意:仓库请求是出库交货订单、过账更改、库存转储(用于仓库中的内部移动)或入库…

最短网络kruskal算法

题目描述 农民约翰被选为他们镇的镇长&#xff01;他其中一个竞选承诺就是在镇上建立起互联网&#xff0c;并连接到所有的农场。当然&#xff0c;他需要你的帮助。约翰已经给他的农场安排了一条高速的网络线路&#xff0c;他想把这条线路共享给其他农场。为了用最小的消费&…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 一、简单介绍 二、简单给视频添加水印图片效果实现…

【保姆级讲解Element UI】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…