PostgreSQL源码分析——物化视图

2024-06-19 05:36

本文主要是介绍PostgreSQL源码分析——物化视图,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们前面分析完视图后,这里再继续分析一下物化视图,其实现原理是不相同的,需要注意,物化视图等于是将返回的结果集缓存起来,而视图是查询重写,结果需要重新进行计算。

create materialized view matvt1 as select * from t1

语法解析部分

主流程如下:

exec_simple_query
--> pg_parse_query--> raw_parser--> base_yyparse
--> pg_analyze_and_rewrite--> parse_analyze--> transformStmt--> transformCreateTableAsStmt--> transformStmt   // 对查询语句进行语义分析,将其转换为查询树Query--> pg_rewrite_query
--> pg_plan_queries

定义物化视图的语法如下:

/*******************************************************************************		QUERY :*				CREATE MATERIALIZED VIEW relname AS SelectStmt******************************************************************************/
CreateMatViewStmt:CREATE OptNoLog MATERIALIZED VIEW create_mv_target AS SelectStmt opt_with_data{CreateTableAsStmt *ctas = makeNode(CreateTableAsStmt);  ctas->query = $7;ctas->into = $5;ctas->relkind = OBJECT_MATVIEW;ctas->is_select_into = false;ctas->if_not_exists = false;/* cram additional flags into the IntoClause */$5->rel->relpersistence = $2;$5->skipData = !($8);$$ = (Node *) ctas;}

创建物化视图的语法,抽象语法树表示CreateTableAsStmt,创建物化视图的流程与CREATE TABLE AS 相同,等于新创建一个表(UNLOGGED TABLE),保存查询到的结果集。可以看到SELECT INTO,与CREATE TABLE AS也是用此进行表示。

/* ----------------------*		CREATE TABLE AS Statement (a/k/a SELECT INTO)** A query written as CREATE TABLE AS will produce this node type natively.* A query written as SELECT ... INTO will be transformed to this form during* parse analysis.* A query written as CREATE MATERIALIZED view will produce this node type,* during parse analysis, since it needs all the same data.** The "query" field is handled similarly to EXPLAIN, though note that it* can be a SELECT or an EXECUTE, but not other DML statements.* ----------------------*/
typedef struct CreateTableAsStmt
{NodeTag		type;Node	   *query;			/* the query (see comments above) */IntoClause *into;			/* destination table */ObjectType	relkind;		/* OBJECT_TABLE or OBJECT_MATVIEW */bool		is_select_into; /* it was written as SELECT INTO */bool		if_not_exists;	/* just do nothing if it already exists? */
} CreateTableAsStmt;/** IntoClause - target information for SELECT INTO, CREATE TABLE AS, and* CREATE MATERIALIZED VIEW** For CREATE MATERIALIZED VIEW, viewQuery is the parsed-but-not-rewritten* SELECT Query for the view; otherwise it's NULL.  (Although it's actually* Query*, we declare it as Node* to avoid a forward reference.)*/
typedef struct IntoClause
{NodeTag		type;RangeVar   *rel;			/* target relation name */List	   *colNames;		/* column names to assign, or NIL */char	   *accessMethod;	/* table access method */List	   *options;		/* options from WITH clause */OnCommitAction onCommit;	/* what do we do at COMMIT? */char	   *tableSpaceName; /* table space to use, or NULL */Node	   *viewQuery;		/* materialized view's SELECT query */bool		skipData;		/* true for WITH NO DATA */
} IntoClause;

执行部分——创建物化视图

主流程如下:

exec_simple_query
--> pg_parse_query
--> pg_analyze_and_rewrite
--> pg_plan_queries
--> PortalStart
--> PortalRun--> PortalRunUtility    // Execute a utility statement inside a portal.--> ProcessUtility--> standard_ProcessUtility--> ProcessUtilitySlow/** 执行步骤:*         1. 创建表,准备存储结果集*         2. 查询重写(物化视图中的查询语句)*         3. 生成查询的执行计划*         4. 执行获取查询语句的结果集*/--> ExecCreateTableAs  // Create the tuple receiver object and insert info it will need-->  CreateIntoRelDestReceiver  // 结果集输入到IntoRel中,新建的表中--> QueryRewrite--> pg_plan_query--> standard_planner--> subquery_planner--> grouping_planner--> query_planner--> make_one_rel--> create_plan--> create_scan_plan--> CreateQueryDesc  /* Create a QueryDesc, redirecting output to our tuple receiver */--> ExecutorStart--> ExecutorRun--> standard_ExecutorRun// 1. 建表--> intorel_startup--> create_ctas_internal    //Actually create the target table--> DefineRelation // 建表--> heap_create_with_catalog--> heap_create--> StoreViewQuery  // Use the rules system to store the query for the view.--> UpdateRangeTableOfViewParse --> DefineViewRules--> DefineQueryRewrite  // Set up the ON SELECT rule.--> InsertRule // 插入的规则,重写为新的物化表,并不是源表--> SetMatViewPopulatedState// 2. 执行查询语句,结果集存入物化的表中--> ExecutePlan--> ExecScan    // 扫描获取tuple--> ExecScanFetch--> SeqNext--> table_beginscan--> intorel_receive  // receive one tuple--> table_tuple_insert  // 将查询到的tuple slot插入到创建的表中--> heapam_tuple_insert--> ExecFetchSlotHeapTuple--> tts_buffer_heap_materialize--> heap_copytuple--> tts_buffer_heap_get_heap_tuple--> heap_insert // 插入到表中,找到指定的page,插入tuple。--> heap_prepare_insert--> RelationPutHeapTuple    --> ExecutorEnd--> PortalDrop

对于结果集中如何存入物化的新表中,可查看dest.c、createas.c等源码,查询到的结果可以按照需求发送到不同的地方,可查看下面的枚举,可以看到有个DestIntoRel的值,即使将结果send to relation

/* ----------------*		CommandDest is a simplistic means of identifying the desired*		destination.  Someday this will probably need to be improved.** Note: only the values DestNone, DestDebug, DestRemote are legal for the* global variable whereToSendOutput.   The other values may be used* as the destination for individual commands.* ----------------*/
typedef enum
{DestNone,					/* results are discarded */DestDebug,					/* results go to debugging output */DestRemote,					/* results sent to frontend process */DestRemoteExecute,			/* sent to frontend, in Execute command */DestRemoteSimple,			/* sent to frontend, w/no catalog access */DestSPI,					/* results sent to SPI manager */DestTuplestore,				/* results sent to Tuplestore */DestIntoRel,				/* results sent to relation (SELECT INTO) */DestCopyOut,				/* results sent to COPY TO code */DestSQLFunction,			/* results sent to SQL-language func mgr */DestTransientRel,			/* results sent to transient relation */DestTupleQueue				/* results sent to tuple queue */
} CommandDest;

其中还有一个非常重要的函数需要列出来CreateIntoRelDestReceiver,查询返回的结果输入到IntoClause节点指定的表中。

/** CreateIntoRelDestReceiver -- create a suitable DestReceiver object** intoClause will be NULL if called from CreateDestReceiver(), in which* case it has to be provided later.  However, it is convenient to allow* self->into to be filled in immediately for other callers.*/
DestReceiver *
CreateIntoRelDestReceiver(IntoClause *intoClause)
{DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));self->pub.receiveSlot = intorel_receive;self->pub.rStartup = intorel_startup;self->pub.rShutdown = intorel_shutdown;self->pub.rDestroy = intorel_destroy;self->pub.mydest = DestIntoRel;self->into = intoClause;/* other private fields will be set during intorel_startup */return (DestReceiver *) self;
}typedef struct
{DestReceiver pub;			/* publicly-known function pointers */IntoClause *into;			/* target relation specification *//* These fields are filled by intorel_startup: */Relation	rel;			/* relation to write to */ObjectAddress reladdr;		/* address of rel, for ExecCreateTableAs */CommandId	output_cid;		/* cmin to insert in output tuples */int			ti_options;		/* table_tuple_insert performance options */BulkInsertState bistate;	/* bulk insert state */
} DR_intorel;

最后我们看一下系统表pg_class、pg_rewrite中的相关信息:

-- 物化视图matvt1
postgres@postgres=# select oid,relname,relkind,relhasrules from pg_class where relname='matvt1';
-[ RECORD 1 ]-------
oid         | 16391
relname     | matvt1
relkind     | m
relhasrules | t
-- 表t1
postgres@postgres=# select oid,relname,relkind,relhasrules,relrewrite from pg_class where relname='t1';
-[ RECORD 1 ]------
oid         | 16384     -- 表OID
relname     | t1        -- 表名
relkind     | r         -- 表示是普通表
relhasrules | f         -- 表是否定义了规则
relrewrite  | 0
-- 查看系统表pg_rewrite,查看插入的规则
postgres@postgres=# select * from pg_rewrite order by oid desc limit 1;
-[ RECORD 1 ]
oid        | 16394
rulename   | _RETURN
ev_class   | 16391
ev_type    | 1
ev_enabled | O
is_instead | t
ev_qual    | <>
ev_action  | ({QUERY :commandType 1 :querySource 0 :canSetTag true :utilityStmt <> :resultRelation 0 :hasAggs false :hasWindowFuncs false :hasTargetSRFs false :hasSubLinks false :hasDistinctOn false :hasRecursive false :hasModifyingCTE false :hasForUpdate false :hasRowSecurity false :cteList <> :rtable ({RTE :alias {ALIAS :aliasname old :colnames <>} :eref {ALIAS :aliasname old :colnames ("a" "b")} :rtekind 0 :relid 16391 :relkind m :rellockmode 1 :tablesample <> :lateral false :inh false :inFromCl false :requiredPerms 0 :checkAsUser 0 :selectedCols (b) :insertedCols (b) :updatedCols (b) :extraUpdatedCols (b) :securityQuals <>} {RTE :alias {ALIAS :aliasname new :colnames <>} :eref {ALIAS :aliasname new :colnames ("a" "b")} :rtekind 0 :relid 16391 :relkind m :rellockmode 1 :tablesample <> :lateral false :inh false :inFromCl false :requiredPerms 0 :checkAsUser 0 :selectedCols (b) :insertedCols (b) :updatedCols (b) :extraUpdatedCols (b) :securityQuals <>} {RTE :alias <> :eref {ALIAS :aliasname t1 :colnames ("a" "b")} :rtekind 0 :relid 16384 :relkind r :rellockmode 1 :tablesample <> :lateral false :inh true :inFromCl true :requiredPerms 2 :checkAsUser 0 :selectedCols (b 8 9) :insertedCols (b) :updatedCols (b) :extraUpdatedCols (b) :securityQuals <>}) :jointree {FROMEXPR :fromlist ({RANGETBLREF :rtindex 3}) :quals <>} :targetList ({TARGETENTRY :expr {VAR :varno 3 :varattno 1 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 3 :varattnosyn 1 :location 42} :resno 1 :resname a :ressortgroupref 0 :resorigtbl 16384 :resorigcol 1 :resjunk false} {TARGETENTRY :expr {VAR :varno 3 :varattno 2 :vartype 23 :vartypmod -1 :varcollid 0 :varlevelsup 0 :varnosyn 3 :varattnosyn 2 :location 42} :resno 2 :resname b :ressortgroupref 0 :resorigtbl 16384 :resorigcol 2 :resjunk false}) :override 0 :onConflict <> :returningList <> :groupClause <> :groupingSets <> :havingQual <> :windowClause <> :distinctClause <> :sortClause <> :limitOffset <> :limitCount <> :limitOption 0 :rowMarks <> :setOperations <> :constraintDeps <> :withCheckOptions <>})

物化视图与普通视图不同的地方在于,创建物化视图时,要建立一张物理表存储查询语句的结果集。

这篇关于PostgreSQL源码分析——物化视图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Win11安装PostgreSQL数据库的两种方式详细步骤

《Win11安装PostgreSQL数据库的两种方式详细步骤》PostgreSQL是备受业界青睐的关系型数据库,尤其是在地理空间和移动领域,:本文主要介绍Win11安装PostgreSQL数据库的... 目录一、exe文件安装 (推荐)下载安装包1. 选择操作系统2. 跳转到EDB(PostgreSQL 的

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用