PostgreSQL数据库分区裁剪——enable_partition_pruning

news2024/11/26 21:24:10

在PostgreSQL 10版本之前,PostgreSQL数据库实际上是没有单独的创建分区表的DDL语句,都是通过表继承的原理来创建分区表,这样使得在PostgreSQL中使用分区表不是很方便,到PostgreSQL 10之后,PostgreSQL扩展了创建表的DDL语句,可以用这个DDL语句来创建分区表,原先使用继承的方式还是可以创建分区表,但这两种分区表是不能混用的。于是PostgreSQL 10增加的分区表叫声明式分区(Declarative Partitioning),原先使用表继承的方式仍然可以实现分区表的功能。而使用继承的方式实现的分区表的分区裁剪是靠设置参数“constraint_exclusion=partition”来实现的,而如果使用了声明式分区表,则需要使用参数“enable_partition_pruning”来控制是否使用分区裁剪功能(Enables plan-time and run-time partition pruning. Allows the query planner and executor to compare partition bounds to conditions in the query to determine which partitions must be scanned)。PostgreSQL支持静态条件分区裁剪,Greenplum通过ORCA 优化器实现了动态分区裁剪。参考关于PostgreSQL的分区表的历史及分区裁剪参数enable_partition_pruning与constraint_exclusion的区别文档对分区裁剪进行学习。

创建声明式分区表

创建声明式分区表SQL如下所示CREATE TABLE ptab01 (id int not null, tm timestamptz not null) PARTITION BY RANGE (tm);。首先我们看一下其抽象查询语法树AST,RawStmt结构体是单个语句的raw解析树的存储结构(container for any one statement’s raw parse tree)。CreateStmt结构体定义在src/include/nodes/parsenodes.h文件中,relation代表需要创建的关系表,tableElts代表列定义,partspec代表PARTITION BY子句。

/* Optional partition key specification */
OptPartitionSpec: PartitionSpec	{ $$ = $1; } | /*EMPTY*/			{ $$ = NULL; }
PartitionSpec: PARTITION BY ColId '(' part_params ')'
				{
					PartitionSpec *n = makeNode(PartitionSpec);
					n->strategy = $3; n->partParams = $5; n->location = @1; $$ = n;
				}

请添加图片描述
该SQL使用的时part_elem的规则一,分区键是列名。从其他规则来看,可以partition by函数表达式或表达式,而不仅仅是列名。

part_params:	part_elem	{ $$ = list_make1($1); } | part_params ',' part_elem			{ $$ = lappend($1, $3); }
part_elem: ColId opt_collate opt_class
				{   PartitionElem *n = makeNode(PartitionElem);
					n->name = $1; n->collation = $2; n->opclass = $3;
					n->expr = NULL;	n->location = @1; $$ = n; }
			| func_expr_windowless opt_collate opt_class
				{   PartitionElem *n = makeNode(PartitionElem);
                    n->expr = $1; n->collation = $2; n->opclass = $3;
					n->name = NULL;	n->location = @1; $$ = n;
				}
			| '(' a_expr ')' opt_collate opt_class
				{ PartitionElem *n = makeNode(PartitionElem);
					n->name = NULL; n->location = @1; $$ = n;
					n->expr = $2; n->collation = $4; n->opclass = $5;				
				}

parse_analyze函数进入查询语句分析transformStmt流程,对于该SQL直接是走右下角的default的流程(result = makeNode(Query); result->commandType = CMD_UTILITY; result->utilityStmt = (Node *) parseTree; break;)。
请添加图片描述
进入pg_rewrite_query函数流程,如下是elog_node_display(LOG, "parse tree", ...)语句打印的查询语句分析transformStmt流程之后的解析树。utilityStmt就是上述流程的抽象查询语法树AST RawStmt结构体。commandType为5代表的是CMD_UTILITY。
请添加图片描述
该SQL不需要经过QueryWrite查询重写过程,将其作为querytree_list的元素,调用elog_node_display(LOG, "rewritten parse tree", querytree_list...)函数输出重写的解析树。
在这里插入图片描述
请添加图片描述
进入执行器,从执行流程可以看出其执行策略是PORTAL_MULTI_QUERY,走default分支的ProcessUtilitySlow函数。首先调用transformCreateStmt进行语法分析(transform阶段下放到这里了),将语法树转为CreateStmt、TableLikeClause、UTILITY_SUBCOMMAND PlannedStmt(由于建表语句中会有serial,check等额外的特性,这些需要额外的PlannedStmt来处理,因此会增加PlannedStmt)。

PortalStart  PORTAL_MULTI_QUERY  Need do nothing now
PortalRun
 | -- PortalRunMulti
       | -- PortalRunUtility   (pstmt->utilityStmt {type = T_CreateStmt, relation = 0x248e830, tableElts = 0x248eba8, inhRelations = 0x0, partbound = 0x0, partspec = 0x248efb0, ofTypename = 0x0, constraints = 0x0, options = 0x0, oncommit = ONCOMMIT_NOOP, tablespacename = 0x0, accessMethod = 0x0, if_not_exists = false})
              | -- ProcessUtility 
                    | -- standard_ProcessUtility
                          | -- ProcessUtilitySlow
                                | case T_CreateStmt:   case T_CreateForeignTableStmt:
                                  | -- List *stmts = transformCreateStmt  <-- transform阶段下放到这里了 
                                  | -- while (stmts != NIL)
                                    | --   Node *stmt = (Node *) linitial(stmts); stmts = list_delete_first(stmts);
                                    | --   if (IsA(stmt, CreateStmt))
                                    | --   else if (IsA(stmt, TableLikeClause))
                                    | --   else  ProcessUtility(wrapper, queryString, false, PROCESS_UTILITY_SUBCOMMAND, params, NULL, None_Receiver, NULL);

建表走如下流程:

CreateStmt *cstmt = (CreateStmt *) stmt;
Datum		toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES;

/* Create the table itself */
address = DefineRelation(cstmt, RELKIND_RELATION, InvalidOid, NULL, queryString);

/* parse and validate reloptions for the toast table */
toast_options = transformRelOptions((Datum) 0, cstmt->options, "toast", validnsps, true, false);
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
NewRelationCreateToastTable(address.objectId,toast_options);

分区裁剪

postgres=# explain select * from ptab01 where tm='2020-01-07'::timestamptz;
                             QUERY PLAN
---------------------------------------------------------------------
 Seq Scan on ptab01_202001  (cost=0.00..80.80 rows=1 width=12)
   Filter: (tm = '2020-01-07 00:00:00-08'::timestamp with time zone)
(2 rows)

从上面可以看出,声明式分区表只扫描了包括指定时间实际的分区ptab01_202001,没有扫描其他时间段的分区。首先我们看一下其抽象查询语法树AST,RawStmt结构体是单个语句的raw解析树的存储结构(container for any one statement’s raw parse tree),也就是elog_node_display(LOG, "raw tree", parseTree, ...)打印出来的解析器输出。Select型查询语句SelectStmt定义在src/include/nodes/parsenodes.h中,如下其包含目标列域targetList、from子句fromClause、where条件whereClause。where条件whereClause结构体执行A_Expr结构体(有如下成员NodeTag typeA_Expr_Kind kindList *nameNode *lexperNode *rexprint location),其中name指明了这是等号条件,左边的等式是ColumnRef,右边是被强制转换的常量’2020-01-07’;targetLost执行RESTARTGET节点,其包含的是COLUMNREF,这里其代表A_START也就是“*”(所有列)[ 从下图可以看出ColumnRef能代表两种类型的节点,一是单列,而是所有列 ]。
在这里插入图片描述
从调用堆栈可以看出transformOptionalSelectInto函数并没有执行有效代码,只是调用了transformStmt,进行后续针对不同类型的Stmt分类处理。走transformSelectStmt函数处理T_SelectStmt节点。

transformOptionalSelectInto (pstate=0x1de9848, parseTree=0x1dc4c80)
  if (IsA(parseTree, SelectStmt))
    SelectStmt *stmt = (SelectStmt *) parseTree;
    while (stmt && stmt->op != SETOP_NONE) --> stmt->op == SETOP_NONE
    if (stmt->intoClause)
return transformStmt(pstate, parseTree); 
transformStmt (pstate=0x1de9848, parseTree=0x1dc4c80) at analyze.c:277
  switch (nodeTag(parseTree))
   case T_SelectStmt:
    SelectStmt *n = (SelectStmt *) parseTree;
    if (n->valuesLists)
    else if (n->op == SETOP_NONE)   <-- 走这个分支
      result = transformSelectStmt(pstate, n);
  return result;
transformSelectStmt (pstate=0x1de9848, stmt=0x1dc4c80) at analyze.c:1200
  Query      *qry = makeNode(Query);
  qry->commandType = CMD_SELECT;
  transformFromClause(pstate, stmt->fromClause);

请添加图片描述
首先对From子句进行转换,transformFromClauseItem函数将会对fromClause列表元素RANGEVAR结构体进行转换,图中的RANGEVAR不是CTE reference/tuplestore reference,所以只能是plain relation reference,调用transformTableEntry函数。

请添加图片描述
下一步会走到transformStmt函数中的T_SelectStmt分支,op为SETOP_NONE,因此会执行transformSelectStmt函数。由于没有with、into、window等子句,直接处理From子句(仅有RangeVar节点),执行transformFromClause子句。

请添加图片描述

请添加图片描述

请添加图片描述

enable_partition_pruning GUC参数出现在query_planner的prune_append_rel_partitions和创建执行计划的create_append_plan、create_merge_append_plan。和constraint exclusion不同的是,enable_partition_pruning GUC参数额外出现在了根据最优路径创建执行计划的流程中。
请添加图片描述
prune_append_rel_partitions函数处理关系表的baserestrictinfo(rel->baserestrictinfo为SQL关联到relation上的SQL谓词表达式列表),利用在查询优化阶段可以evaluated的quals表达式去确定分区的最小集合,并返回包含匹配分区indexes(该index用于rel’s part_rels array数组的寻址)的Bitmapset(静态分区裁剪)。执行流程如下:

Bitmapset *prune_append_rel_partitions(RelOptInfo *rel){
	List	   *clauses = rel->baserestrictinfo;
	List	   *pruning_steps; GeneratePruningStepsContext gcontext;
	
	if (rel->nparts == 0) return NULL; /* If there are no partitions, return the empty set */	
	if (!enable_partition_pruning || clauses == NIL) return bms_add_range(NULL, 0, rel->nparts - 1); /* If pruning is disabled or if there are no clauses to prune with, return all partitions. */

	/* Process clauses to extract pruning steps that are usable at plan time. If the clauses are found to be contradictory, we can return the empty set. */
	gen_partprune_steps(rel, clauses, PARTTARGET_PLANNER,&gcontext);
	if (gcontext.contradictory) return NULL;
	pruning_steps = gcontext.steps;	
	if (pruning_steps == NIL) return bms_add_range(NULL, 0, rel->nparts - 1); /* If there's nothing usable, return all partitions */
  
	/* Set up PartitionPruneContext */
	PartitionPruneContext context;
	context.strategy = rel->part_scheme->strategy; context.partnatts = rel->part_scheme->partnatts;
	context.nparts = rel->nparts; context.boundinfo = rel->boundinfo;
	context.partcollation = rel->part_scheme->partcollation; context.partsupfunc = rel->part_scheme->partsupfunc;
	context.stepcmpfuncs = (FmgrInfo *) palloc0(sizeof(FmgrInfo) *context.partnatts *list_length(pruning_steps));
	context.ppccontext = CurrentMemoryContext;
	/* These are not valid when being called from the planner */
	context.planstate = NULL; context.exprcontext = NULL; context.exprstates = NULL;
	
	return get_matching_partitions(&context, pruning_steps); /* Actual pruning happens here. */
}

执行堆栈:

query_planner --> add_other_rels_to_query --> expand_inherited_rtentry --> expand_partitioned_rtentry --> prune_append_rel_partitions
                                                                            expand_inherited_rtentry --> expand_appendrel_subquery --> expand_inherited_rtentry --> expand_partitioned_rtentry --> prune_append_rel_partitions

在这里插入图片描述
create_append_plan和create_merge_append_plan函数在创建执行计划阶段进行分区裁剪,关于enable_partition_pruning为true分支,这两种情况执行都很相似,都是为执行期间进行分区裁剪收集信息(动态分区裁剪),并创建PartitionPruneInfo结构体存放该信息。

static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags){
    ...
	/* If any quals exist, they may be useful to perform further partition pruning during execution.  Gather information needed by the executor to do partition pruning. */
	if (enable_partition_pruning){
		List	   *prunequal = extract_actual_clauses(rel->baserestrictinfo, false);
		if (best_path->path.param_info){
			List	   *prmquals = best_path->path.param_info->ppi_clauses;
			prmquals = extract_actual_clauses(prmquals, false); prmquals = (List *) replace_nestloop_params(root,(Node *) prmquals);
			prunequal = list_concat(prunequal, prmquals);
		}
		if (prunequal != NIL)
			partpruneinfo = make_partition_pruneinfo(root, rel, best_path->subpaths, prunequal);
	}

	plan->appendplans = subplans; plan->nasyncplans = nasyncplans; plan->first_partial_plan = best_path->first_partial_path; // or node->mergeplans = subplans;
	plan->part_prune_info = partpruneinfo;
}

执行堆栈:
create_plan_recurse --> case T_Append: create_append_plan
create_merge_append_plan
create_plan_recurse --> case T_MergeAppend: create_merge_append_plan
在这里插入图片描述
src/backend/optimizer/plan/setrefs.c/set_append_references中会对part_prune_info中的rtindex进行修正,其调用者set_plan_references函数在create_plan创建执行计划之后。

static Plan *set_append_references(PlannerInfo *root, Append *aplan, int rtoffset)
	if (aplan->part_prune_info){
		foreach(l, aplan->part_prune_info->prune_infos){
			List	   *prune_infos = lfirst(l); ListCell   *l2;
			foreach(l2, prune_infos){
				PartitionedRelPruneInfo *pinfo = lfirst(l2);
				pinfo->rtindex += rtoffset;
			}
		}
	}
standard_planner --> set_plan_references --> set_plan_refs --> set_append_references --> set_plan_refs --> set_append_references
                                                           --> set_customscan_references --> set_plan_refs --> set_append_references
                                      
                                                           --> set_subqueryscan_references --> set_plan_references

在src/backend/executor/nodeAppend.c文件中Append节点初始化ExecInitAppend流程中包含了动态分区裁剪初始化的流程(同样MergeAppend节点初始化ExecInitMergeAppend也包含相同流程),如下所示:

AppendState *ExecInitAppend(Append *node, EState *estate, int eflags){

	/* If run-time partition pruning is enabled, then set that up now */
	if (node->part_prune_info != NULL){
		/* Set up pruning data structure.  This also initializes the set of subplans to initialize (validsubplans) by taking into account the result of performing initial pruning if any. */
		PartitionPruneState *prunestate = ExecInitPartitionPruning(&appendstate->ps, list_length(node->appendplans), node->part_prune_info, &validsubplans);
		appendstate->as_prune_state = prunestate;
		nplans = bms_num_members(validsubplans);
		/* When no run-time pruning is required and there's at least one subplan, we can fill as_valid_subplans immediately, preventing later calls to ExecFindMatchingSubPlans. */
		if (!prunestate->do_exec_prune && nplans > 0)
			appendstate->as_valid_subplans = bms_add_range(NULL, 0, nplans - 1);
	}else{
		nplans = list_length(node->appendplans);
		/* When run-time partition pruning is not enabled we can just mark all subplans as valid; they must also all be initialized. */
		appendstate->as_valid_subplans = validsubplans = bms_add_range(NULL, 0, nplans - 1);
		appendstate->as_prune_state = NULL;
	}
create table ptab01_202001 partition of ptab01 for values from ('2020-01-01') to ('2020-02-01');
create table ptab01_202002 partition of ptab01 for values from ('2020-02-01') to ('2020-03-01');
create table ptab01_202003 partition of ptab01 for values from ('2020-03-01') to ('2020-04-01');
create table ptab01_202004 partition of ptab01 for values from ('2020-04-01') to ('2020-05-01');
create table ptab01_202005 partition of ptab01 for values from ('2020-05-01') to ('2020-06-01');
insert into ptab01 select extract(epoch from seq), seq from generate_series('2020-01-01'::timestamptz, '2020-05-31 23:59:59'::timestamptz, interval '10 seconds') as seq;

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

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

相关文章

idea插件开发-PSI

程序结构接口&#xff08;Program Structure Interface&#xff09;简称PSI&#xff0c;PSI是IDEA插件开发最复杂的一块内容&#xff0c;后续会有大量实战来强化理解此处的知识。PSI是IntelliJ 平台中的一个层&#xff0c;负责解析文件并创建语法和语义代码模型&#xff0c;为平…

Linux 导入MySQL数据库(四)

文章目录 一、导出数据库二、导入数据库&#xff08;方法一&#xff09;1. 通过FinalShell连接服务器&#xff0c;登录mysql&#xff1a;2. 新建数据库3. 使用新建的数据库4. 对数据库进行编码设置5. 从路径中导入 SQL 文件数据 三、导入数据库&#xff08;方法二&#xff09;【…

为uni-cloud(Dcloud国产之辉)声明!

目录 uni-cloud的介绍 uni-cloud与uni-app的关系 uni-cloud与云原生的关系 uni-cloud的开发优点 uni-cloud与HBuilder X结合的优越性 uni-cloud高效解决"高并发" uni-cloud与阿里云、腾讯云完美结合 uni-cloud背后庞大的插件市场 美中不足 加油&#xff01…

chatgpt赋能python:Python代码保存:如何保存你的Python代码?

Python代码保存&#xff1a;如何保存你的Python代码&#xff1f; Python被广泛认为是学习编程的入门语言之一&#xff0c;因为它易于学习和使用&#xff0c;并且拥有大量的库和框架来处理各种任务。 在编写Python代码时&#xff0c;你可能会像大多数编程任务一样&#xff0c;…

实践项目三: 校园兼职平台(合作重构版)

项目说明 1 据了解&#xff0c;目前在校大学生80%以上有做兼职的需求&#xff0c;兼职打工已经不仅仅是经济困难的学生赚取生活费用的途径。调查显示&#xff0c;全球经济危机对就业产生冲击&#xff0c;用人单位对人员的社会实践能力要求提高&#xff0c;大学期间必要的社会实…

Java学习笔记(StringJoiner和集合)

StringJoiner StringJoiner与StringBuilder一样&#xff0c;也可以看成是一个容器&#xff0c;创建之后的内容是可变的 作用&#xff1a;提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有人用 构造方法&#xff1a; 方法名 说明 pub…

四、HAL_驱动机械按键

1、开发环境。 (1)KeilMDK&#xff1a;V5.38.0.0 (2)STM32CubeMX&#xff1a;V6.8.1 (3)MCU&#xff1a;STM32F407ZGT6 2、机械按键简介 (1)按键内部是机械结构&#xff0c;也就是内部是没有电路的。按键按下内部引脚导通&#xff0c;松开内部断开。 3、实验目的&原理…

Git、Github、Gitee的区别

⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;Git 目录 1、Git2、Gitee3、GitHub 什么是版本管理&#xff1f;   版本管理是管理各个不同的版本&#xff0c;出了问题可以及时回滚。 1、Git Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理代码的变化。它是…

开源代码分享(2)—综合能源系统零碳优化调度

参考文献&#xff1a; Optimal dispatch of zero-carbon-emission micro Energy Internet integrated with non-supplementary fired compressed air energy storage system | SGEPRI Journals & Magazine | IEEE Xplore 1.引言 全球能源危机和环境污染的双重压力促使能量…

mysql 删表引出的问题

背景 将测试环境的表同步到另外一个数据库服务器中&#xff0c;但有些表里面数据巨大&#xff0c;&#xff08;其实不同步该表的数据就行&#xff0c;当时没想太多&#xff09;&#xff0c;几千万的数据&#xff01;&#xff01; 步骤 1. 既然已经把数据同步过来的话&#x…

chatgpt赋能python:Python怎么从1加到100

Python怎么从1加到100 Python是一种面向对象的编程语言&#xff0c;随着人工智能和大数据技术的流行&#xff0c;Python也变得越来越受欢迎。Python有很多优点&#xff0c;其中之一就是易于学习和使用。在这篇文章中&#xff0c;我们将介绍如何用Python从1加到100。 前置知识…

还在为浏览量焦虑吗?为何不用R语言来做归因分析找出痛点

一、引言 大家好&#xff0c;我是一名博客作者&#xff0c;同时也是一个有着浏览量焦虑症的患者。每次发一篇新的博客文章&#xff0c;我总是不停地刷新页面&#xff0c;看看有多少人来访问、阅读和留言。当发现访问量不如自己预期时&#xff0c;我就会有一种被冷落、被忽视的…

【DFT】MBIST (1) MBIST基础

MBSIT基础 1. 存储器测试2. 存储器结构3. 存储器故障模型3.1 固定故障(SAF)3.2 转换故障(TF)3.3 耦合故障(CF)3.4 桥接和状态耦合故障 4. 功能测试方法4.1 March 测试算法4.2 March-C 算法4.3 MATS 算法4.4 其他的 March 测试 5. MBSIT方法5.1 简单的 March MBIST1. 简单的Marc…

灵动超值系列FTHR-G0140开发板

文章目录 引言MM32G0140微控制器FTHR-G0140电路板MM32G0140最小核心系统供电系统可编程按键和小灯扩展插座 MindSDK软件开发平台 引言 2023年上半年的一些活动现场&#xff08;包括但不限于4月在苏州的全国高校电子信息类专业教学论坛、5月和6月在同济大学、四川大学、南京大学…

Vue3+Vite+TypeScript常用项目模块详解

目录 1.Vue3ViteTypeScript 概述 1.1 vue3 1.1.1 Vue3 概述 1.1.2 vue3的现状与发展趋势 1.2 Vite 1.2.1 现实问题 1.2 搭建vite项目 1.3 TypeScript 1.3.1 TypeScript 定义 1.3.2 TypeScript 基本数据类型 1.3.3 TypeScript语法简单介绍 2. 项目配置简单概述 2.…

chatgpt赋能python:如何在Python中二次运行同一个命令语句

如何在Python中二次运行同一个命令语句 如果您是一个熟练的Python开发者&#xff0c;一定会遇到必须二次运行同一个命令语句的情况。在本文中&#xff0c;我们将探讨Python中的几种方法来实现这一目标。 方法1&#xff1a;使用Python Shell Python Shell是Python解释器的一个…

R 语言学习笔记

1. 基础语法 赋值 a 10; b <- 10;# 表示流向&#xff0c;数据流向变量&#xff0c;也可以写成10 -> b创建不规则向量 不用纠结什么是向量&#xff0c;就当作一个容器&#xff0c;数据类型要相同 a c("我","爱","沛")创建一定规则的向…

编译原理 | 课程设计 — 语法分析

第1关&#xff1a;使用C/C语言编写PL/0编译程序的语法分析程序 1、任务描述 基于第二章的词法分析程序&#xff0c;使用C/C语言编写PL/0编译程序的语法分析程序。 2、编程要求 完成上述编程任务&#xff0c;将C/C语言源程序复制粘贴到右侧代码编辑器&#xff0c;点击“评测”按…

bthclsbthclsbthcls

Sql简单查询 创建数据库/表 进入数据库&#xff1a;mysql -uroot -p123456 支持中文字符&#xff1a; Set character_set_databaseutf8; Set character_set_serverutf8; 1.创建数据库 create database demo; use demo; 2.创建数据表 create table score( id int primar…

Day_48堆排序

目录 一. 关于堆排序 1. 堆的定义 二. 堆排序的实现 1. 堆排序的思路 2. 堆排序的问题分析 3. 堆排序的具体实施 4. 效率分析 三. 堆排序的代码实现 1. 堆排序 2. 调整堆&#xff08;核心代码&#xff09; 四. 代码展示 五. 数据测试 六. 总结 一. 关于堆排序 1. 堆的定义…