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

news2024/11/15 7:57:03

问题

为什么每次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

后续流程相同。

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

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

相关文章

如何安装Windows版VRTE2.1.0开发环境并进行开发

前言&#xff08;Abstract&#xff09; 本文档记录了如何安装Windows版VRTE2.1.0开发环境并进行开发&#xff0c;并且总结了当部署在安装了比较陈旧版本Linux内核&#xff08;如<4.5&#xff09;和库的板子上所遭遇的困难&#xff0c;如S32V234EVB。 Definitions and Abbre…

自动化测试selenium(2)

目录 WebDriver介绍 WebDriver使用 使用WebDriver驱动操作浏览器(打开一个百度) WebDriver 相关API 定位元素 操作元素 上一篇主要介绍了自动化测试的概念以及selenium的基本原理, 这里我们来讲一下如何利用selenium来写测试用的脚本. WebDriver介绍 Selenium是一个用于…

Zookeeper(从入门到掌握)看完这一篇就够了

文章目录 一、初识 Zookeeper1.Zookeeper 概念2.Zookeeper 数据模型3.Zookeeper 服务端常用命令4.Zookeeper 客户端常用命令 二、ZooKeeper JavaAPI 操作1.Curator 介绍1.Curator API 常用操作&#xff08;1&#xff09;建立连接&#xff08;2&#xff09;添加节点&#xff08;…

对链表的进一步认识

********以下内容均是个人理解&#xff0c;个人语言&#xff0c;仅代表个人观点&#xff0c;希望能对你有所帮助*************** 1.对链表的进一步深入理解分析 &#xff08;1&#xff09;逻辑结构&#xff1a;想象出来的&#xff0c;并不是真实存在的&#xff0c;例如里面的…

idm线程越多越好吗 idm线程数多少合适

IDM&#xff08;Internet Download Manager&#xff09;是一款流行的下载管理软件&#xff0c;它支持多线程下载&#xff0c;这意味着它可以同时建立多个连接来下载文件的不同部分&#xff0c;从而提高下载速度。我们在使用IDM的时候总是有很多疑问&#xff0c;今天我们学习IDM…

HIT The Wiorld,HIT世界官网地址+配置要求+测试时间+加速器分享

HIT The Wiorld&#xff0c;HIT世界官网地址配置要求测试时间加速器分享 NEXON新游《HIT&#xff1a;世界&#xff08;HIT&#xff1a;The World&#xff09;》将在4月17日上线&#xff0c;目前已在官网开启事前预约预创建角色。Hit :the world&#xff08;HIT:世界&#xff…

鸿蒙 Failed :entry:default@CompileResource...

Failed :entry:defaultCompileResource... media 文件夹下有文件夹或者图片名称包含中文字符 rawfile 文件夹下文件名称、图片名称不能包含中文字符

说说你对链表的理解?常见的操作有哪些?

一、是什么 链表&#xff08;Linked List&#xff09;是一种物理存储单元上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的&#xff0c;由一系列结点&#xff08;链表中每一个元素称为结点&#xff09;组成 每个结点包括两个部分&…

Hdevelop编辑器常用功能

1、灰度直方图 【阈值分割】——对应算子threshold 通过菜单【可视化】-【工具】-【灰度直方图】打开&#xff0c;打开后选中【变量窗口】的某张图片即可进行灰度直方图分析。 刚打开并选中某张图片&#xff1a; 调节【最小化】和【最大化】的两个竖线&#xff0c;此时图中绿…

Spectre漏洞 v2 版本再现,影响英特尔 CPU + Linux 组合设备

近日&#xff0c;网络安全研究人员披露了针对英特尔系统上 Linux 内核的首个原生 Spectre v2 漏洞&#xff0c;该漏洞是2018 年曝出的严重处理器“幽灵”&#xff08;Spectre&#xff09;漏洞 v2 衍生版本&#xff0c;利用该漏洞可以从内存中读取敏感数据&#xff0c;主要影响英…

笔记84:关于递归法的一些感悟

题目1&#xff1a;二叉树的前序遍历 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(…

C# 关于进程回收管理的一款工具设计与分享

目录 设计初衷 开发运行环境 Craneoffice ProcessGC 运行主界面 管理任务与策略 其它设置 移动存储设备管理 核心代码-计时器监控 小结 设计初衷 在使用 COM 模式操作 OFFICE 组件的开发过程中&#xff0c;当操作完相关文档后&#xff0c;在某些情况下仍然无法释放掉…

2024.4.19 Python爬虫复习day07 可视化3

综合案例 需求: 已知2020年疫情数据,都是json数据,需要从文件中读出,进行处理和分析,最终实现数据可视化折线图 相关知识点: json json简介: 本质是一个特定格式的字符串 举例: [{},{},{}] 或者 {}python中json包: import jsonpython数据转为json数据: 变量接收json…

Unity类银河恶魔城学习记录12-17 p139 In game UI源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili UI.cs using UnityEngine;public class UI : MonoBehaviour {[SerializeFie…

kworker(kworker/u2:1,kworker/0:13,kworker/0:1H) 工作队列的命名

1、概述 工作队列是除软中断和tasklet以外最常用的下半部机制之一。工作队列的基本原理是把work(需要推迟执行的函数)交由一个内核线程来异步执行。关于工作队列的具体使用请读者参考其他资料&#xff0c;本文不再概述。 在创建工作队列时,可以通过flag参数指定创建的工作队列…

(四)C++自制植物大战僵尸游戏启动流程

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/ErelL 一、启动方式 鼠标左键单机VS2022上方工具栏中绿色三角按钮&#xff08;本地Windows调试器&#xff09;进行项目启动。第一次启动项目需要编译项目中所有代码文件&#xff0c;编译生成需要一定的时间。不同性能的电…

AVB简介(二): gPTP简介

AVB简介&#xff08;二&#xff09;: gPTP简介 一、时间同步要解决的问题二、gPTP的主要思想2.1 体系结构2.2 主时钟选取2.3 绝对时间同步2.4 相对时间同步 三、影响校时精度的因素3.1 传输时延不对称3.2 驻留时间3.3 时间戳采样点3.4 时钟频率3.5 传输路径延时测量方式3.6 时钟…

idea运行Tomcat,控制台日志的中文乱码

一 版本 win10&#xff0c;idea2022,jdk18,tomcat9 二 问题描述 在idea上可以运行Tomcat。服务器启动后&#xff0c;可以正常访问本地的html文件。但是控制台的Tomcat日志出现了乱码&#xff1a;server与Tomcat Catlina Log两处。 三 无效的解决之道 1 idea的Help选项Edit …

Spring ORM

Spring Data JPA 作为Spring Data 中对于关系型数据库支持的一种框架技术,属于 ORM 的一种,通过得当的使用,可以大大简化开发过程中对于数据操作的复杂度。 Java里面写的一段DB操作逻辑,是如何一步步被传递到 DB 中执行了的呢?为什么 Java 里面可以去对接不同产商的 DB 产…

ExtendSim花生酱加工厂模型

该模型展示了ExtendSim可靠性模块与ExtendeSim离散速率技术相结合的协同作用。 在花生酱加工厂的最初阶段&#xff0c;花生经过烘烤和冷却。冷却后的花生经过热烫或水烫去外皮。这些经过漂白的花生进入过程的混合部分&#xff0c;在研磨机中用盐、葡萄糖和氢化油稳定剂将其粉碎…