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

2024-04-16 11:36

本文主要是介绍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

后续流程相同。

这篇关于Postgresql源码(125)游标恢复执行的原理分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/908680

相关文章

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

MySQL中的表连接原理分析

《MySQL中的表连接原理分析》:本文主要介绍MySQL中的表连接原理分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、背景2、环境3、表连接原理【1】驱动表和被驱动表【2】内连接【3】外连接【4编程】嵌套循环连接【5】join buffer4、总结1、背景

Golang如何对cron进行二次封装实现指定时间执行定时任务

《Golang如何对cron进行二次封装实现指定时间执行定时任务》:本文主要介绍Golang如何对cron进行二次封装实现指定时间执行定时任务问题,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录背景cron库下载代码示例【1】结构体定义【2】定时任务开启【3】使用示例【4】控制台输出总结背景

postgresql数据库基本操作及命令详解

《postgresql数据库基本操作及命令详解》本文介绍了PostgreSQL数据库的基础操作,包括连接、创建、查看数据库,表的增删改查、索引管理、备份恢复及退出命令,适用于数据库管理和开发实践,感兴... 目录1. 连接 PostgreSQL 数据库2. 创建数据库3. 查看当前数据库4. 查看所有数据库

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

python中Hash使用场景分析

《python中Hash使用场景分析》Python的hash()函数用于获取对象哈希值,常用于字典和集合,不可变类型可哈希,可变类型不可,常见算法包括除法、乘法、平方取中和随机数哈希,各有优缺点,需根... 目录python中的 Hash除法哈希算法乘法哈希算法平方取中法随机数哈希算法小结在Python中,

Java Stream的distinct去重原理分析

《JavaStream的distinct去重原理分析》Javastream中的distinct方法用于去除流中的重复元素,它返回一个包含过滤后唯一元素的新流,该方法会根据元素的hashcode和eq... 目录一、distinct 的基础用法与核心特性二、distinct 的底层实现原理1. 顺序流中的去重