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.hqwc.cn/news/623303.html

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

相关文章

WPF中DataGrid主从数据(父子数据)展示

在wpf中可以使用DataGrid控件,进行主从数据展示,也称父子数据展示。下面展示纯原生控件编码实现功能(样式自己可以根据需求进行修改)。 效果如下: 点击图标,展开和收缩可以自由的切换,也可以自己重新写一个样式,比如+,-或者类似图标的样式,都是可以的。 1.首先创建一…

视频制作|专业人做专业事,分分钟剪出“薪”高度

随着视频的蓬勃发展&#xff0c;很多企业都有了通过视频传递企业信息和宣传企业形象的需求&#xff0c;如短视频、长视频、品宣视频、产品视频等。在了解短视频制作的好处与用途之后&#xff0c;视频在各行各业中都有了广泛的应用。 越来越多的企业选择在YesPMP平台将视频制作业…

GT资源-CPLL QPLL

一、前言 QPLL与CPLL是两种为GT Channel提供时钟的锁相环&#xff0c;其中CPLL与GT Channel绑定&#xff0c;每一个通道都有一个CPLL&#xff0c;而QPLL是与Quad绑定&#xff0c;每一个Quad有一个QPLL&#xff0c;4个通道共享一个QPLL 二、CPLL 每个GTX/GTH收发器通道包含一…

Java基础(变量)

什么是变量&#xff1f; 变量&#xff1a;在程序的执行过程中&#xff0c;其值有可能发生改变的量&#xff08;数据&#xff09; 变量的使用场景 当某个数据经常发生改变时&#xff0c;我们也可以用变量储存。当数据变化时&#xff0c;只要修改变量里面记录的值即可。 变量…

iframe嵌入海康威视摄像头监控视频画面

前言&#xff1a;海康威视有非常好的开放平台支持(海康开放平台)&#xff0c;如遇到技术问题&#xff0c;可以先花点时间在开放平台视频教程板块学习一下。直接问客服可能会比较懵&#xff0c;而且sdk客服和api客服互相分离&#xff0c;一开始可能都不知道问谁。 在开放平台上…

linux三剑客精妙招式都汇总在本文了(建议收藏)

熟悉linux的都知道&#xff0c;linux中有著名的三个命令&#xff0c;即grep、sed、awk。这三个命令被称为linux三剑客。三剑客包含各种招式众多&#xff0c;熟练掌握这三个命令的用法&#xff0c;将大大提高我们对文件的处理速度&#xff0c;大大提升运维效率。 本文通过具体实…

Python零基础从小白打怪升级中~~~~~~~TCP网络编程

TCP网络编程 一、什么是TCP协议 TCP( Transmission control protocol )即传输控制协议&#xff0c;是一种面向连接、可靠的数据传输协议&#xff0c;它是为了在不可靠的互联网上提供可靠的端到端字节流而专门设计的一个传输协议。 面向连接 &#xff1a;数据传输之前客户端和…

【环境】原则

系列文章目录 【引论一】项目管理的意义 【引论二】项目管理的逻辑 【环境】概述 【环境】原则 一、培养项目系统性思维 1.1 系统性思维 1.2 系统性思维的价值 1.3 建模和推演&数字孪生 二、项目的复杂性和如何驾驭复杂性 2.1 复杂性的三个维度 2.2 如何驾驭复杂性 三、…

民航电子数据库:[E14024]事务内变更操作次数超过最大许可值10000,可通过系统参数max_trans_modify适当调整限制

目录 一、场景二、异常情况三、原因四、排查五、解决 一、场景 1、对接民航电子数据 2、执行delete语句时报错 二、异常情况 三、原因 通过报错信息就可以看出&#xff0c;是系统参数max_trans_modify配置导致 当删除的数据量 > max_trans_modify时&#xff0c;删除就会…

BypassUAC漏洞挖掘和代码集成

什么是UAC UAC是UserAccountControl的缩写&#xff0c;即用户帐户控制。是Windows操作系统中的一种安全特性&#xff0c;旨在保护计算机不被未经授权的应用程序和操作所破坏。UAC通过提示用户是否允许某个应用程序或操作修改计算机的设置或访问敏感数据&#xff0c;来帮助用户…

【算法一则】编辑距离 【动态规划】

题目 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符 删除一个字符 替换一个字符 示例 1&#xff1a;输入&#xff1a;word1 "horse", word2 "…

MySQL学习笔记4——时间函数

MySQL时间函数 一、日期时间类型1、获取日期时间数据中部分信息的函数2、计算日期时间的函数3、其他日期时间函数 一、日期时间类型 时间函数就是用来处理时间的函数。时间&#xff0c;几乎可以说是各类项目中都会存在的数据&#xff0c;项目需求不同&#xff0c;我们需要的时…