postgresq-自定义执行计划(custom plan)与generic plan(通用执行计划)

文章目录


之前写过一篇关于 PostgreSQL prepare sql的文章,但当时没有提到generic plan(通用计划)和custom plan(自定义计划)这两个概念。现在将通过举例介绍这两个概念。

创建测试表:

postgres=# create database demo;
CREATE DATABASE
postgres=# \c demo
You are now connected to database "demo" as user "postgres".
demo=# create table t1(a int,b text);
CREATE TABLE
demo=# insert into t1 select i,'aaa' from generate_series(1,100) i;
INSERT 0 100
demo=# insert into t1 select i,'bbb' from generate_series(101,200) i;
INSERT 0 100
demo=# insert into t1 select i,'ccc' from generate_series(201,300) i;
INSERT 0 100

在有了数据之后,可以创建一个预处理语句(prepared statement),这个语句的结构是固定的,但允许在执行时插入不同的参数值。这样可以实现代码重用,并提高查询的执行效率。

demo=# prepare pre_stmt as select * from t1 where b=$1;
PREPARE

在 PostgreSQL 中,预处理语句(prepared statements)会在当前会话中注册。如果想查看当前会话中有哪些预处理语句已经被创建并可用,可以通过查询系统视图 pg_prepared_statements 来获取这些信息。

   name   |                    statement                     |         prepare_time          | parameter_types |  result_types  | from_sql | generic_plans | custom_plans
----------+--------------------------------------------------+-------------------------------+-----------------+----------------+----------+---------------+--------------pre_stmt | prepare pre_stmt as select * from t1 where b=$1; | 2025-01-01 18:32:43.697846+07 | {text}          | {integer,text} | t        |             0 |            0
(1 row)

当我们对一个语句运行 EXPLAIN (ANALYZE) 时,可以看到 PostgreSQL 为该语句生成的实际执行计划以及执行的统计信息。这包括查询是如何被优化和执行的,例如是否使用了索引、查询的执行时间、返回的行数等。

demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.032..0.252 rows=100 loops=1)Filter: (b = 'aaa'::text)Rows Removed by Filter: 200Planning Time: 0.568 msExecution Time: 0.287 ms
(5 rows)

在执行计划的“Filter”行中,第一次执行时,执行计划会显示你传入的实际参数值(比如 ‘aaa’),这时是 自定义计划(custom plan)。但是,当你多次执行这个预处理语句时,执行计划中filter行的参数值会变成显示占位符(如 $1),这时是 通用计划(generic plan)。

demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.107..0.255 rows=100 loops=1)Filter: (b = 'aaa'::text)Rows Removed by Filter: 200Planning Time: 0.106 msExecution Time: 0.308 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.045..0.255 rows=100 loops=1)Filter: (b = 'aaa'::text)Rows Removed by Filter: 200Planning Time: 0.144 msExecution Time: 0.297 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.049..0.259 rows=100 loops=1)Filter: (b = 'aaa'::text)Rows Removed by Filter: 200Planning Time: 0.293 msExecution Time: 0.309 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.041..0.248 rows=100 loops=1)Filter: (b = 'aaa'::text)Rows Removed by Filter: 200Planning Time: 0.279 msExecution Time: 0.289 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ('aaa');QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.034..0.215 rows=100 loops=1)Filter: (b = $1)Rows Removed by Filter: 200Planning Time: 0.101 msExecution Time: 0.245 ms
(5 rows)

在执行查询时,最开始会看到执行计划中显示实际传入的参数值,但随着执行次数的增加,执行计划会变成使用占位符(例如 $1)来表示参数。此时,查询使用的是通用计划。通用计划一旦生成后,无论传入什么不同的参数值,它的执行计划都不会再发生变化,直到该预处理语句结束。

demo=# explain (analyze) execute pre_stmt ( 'bbb' );QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.111..0.367 rows=100 loops=1)Filter: (b = $1)Rows Removed by Filter: 200Planning Time: 0.181 msExecution Time: 0.412 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ( 'ccc' );QUERY PLAN
------------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.165..0.249 rows=100 loops=1)Filter: (b = $1)Rows Removed by Filter: 200Planning Time: 0.025 msExecution Time: 0.289 ms
(5 rows)demo=# explain (analyze) execute pre_stmt ( null );QUERY PLAN
----------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.75 rows=100 width=8) (actual time=0.232..0.233 rows=0 loops=1)Filter: (b = $1)Rows Removed by Filter: 300Planning Time: 0.022 msExecution Time: 0.255 ms
(5 rows)

如果你查看 PostgreSQL 的源代码(特别是 src/backend/utils/cache/plancache.c 文件),你将能理解为什么在执行 5 次之后,执行计划会发生变化。
这个文件包含了与查询计划缓存相关的逻辑,解释了为什么预处理语句在执行多次后会从“自定义计划”变为“通用计划”。

/** choose_custom_plan: choose whether to use custom or generic plan** This defines the policy followed by GetCachedPlan.*/
static bool
choose_custom_plan(CachedPlanSource *plansource, ParamListInfo boundParams)
{double          avg_custom_cost;/* One-shot plans will always be considered custom */if (plansource->is_oneshot)return true;/* Otherwise, never any point in a custom plan if there's no parameters */if (boundParams == NULL)return false;/* ... nor for transaction control statements */if (IsTransactionStmtPlan(plansource))return false;/* See if caller wants to force the decision */if (plansource->cursor_options & CURSOR_OPT_GENERIC_PLAN)return false;if (plansource->cursor_options & CURSOR_OPT_CUSTOM_PLAN)return true;/* Generate custom plans until we have done at least 5 (arbitrary) */if (plansource->num_custom_plans < 5)return true;

一旦 PostgreSQL 使用了通用计划,即使数据改变并重新分析表,执行计划也不会再改变。

demo=# insert into t1 select i,'ddd' from generate_series(201,210) i;
INSERT 0 10
demo=# insert into t1 select i,'eee' from generate_series(211,211) i;
INSERT 0 1
demo=# analyze t1;
ANALYZE
demo=# select b,count(*) from t1 group by b order by b;b  | count
-----+-------aaa |   100bbb |   100ccc |   100ddd |    10eee |     1
(5 rows)
demo=# explain (analyze) execute pre_stmt('ddd');QUERY PLAN
----------------------------------------------------------------------------------------------Seq Scan on t1  (cost=0.00..5.89 rows=62 width=8) (actual time=0.059..0.070 rows=10 loops=1)Filter: (b = $1)Rows Removed by Filter: 301Planning Time: 0.021 msExecution Time: 0.184 ms
(5 rows)

当数据量增加、数据分布不均匀,并且在某一列(比如“b”列)上建立了索引时,查询的执行计划可能会发生变化,PostgreSQL 会根据这些因素选择不同的执行策略。

demo=# insert into t1 select i, 'aaa' from generate_series (1,2000000) i;
INSERT 0 1000000
demo=# insert into t1 select i, 'bbb' from generate_series (1000001,3000000) i;
INSERT 0 1000000
demo=# insert into t1 select i, 'ccc' from generate_series (2000001,3000000) i;
INSERT 0 1000000
demo=# insert into t1 select i, 'eee' from generate_series (3000001,3000010) i;
INSERT 0 10
demo=# create index idx_b on t1(b);
CREATE INDEX
demo=#  select b,count(*) from t1 group by b order by b;b  |  count
-----+---------aaa | 1000000bbb | 1000000ccc | 1000000eee |      10(4 rows)

无论我们执行这个查询多少次(查询的是参数‘eee’),PostgreSQL 都不会使用通用计划,而是会持续使用自定义计划。

demo=# explain (analyze) execute pre_stmt('eee');QUERY PLAN
-----------------------------------------------------------------------------------------------------------Index Scan using idx_b on t1  (cost=0.43..4.45 rows=1 width=8) (actual time=0.039..0.048 rows=10 loops=1)Index Cond: (b = 'eee'::text)Planning Time: 0.287 msExecution Time: 0.076 ms
(4 rows)
------>重复执行多次,但至少执行 10 次。
demo=# explain (analyze) execute pre_stmt('eee');QUERY PLAN
-----------------------------------------------------------------------------------------------------------Index Scan using idx_b on t1  (cost=0.43..4.45 rows=1 width=8) (actual time=0.037..0.045 rows=10 loops=1)Index Cond: (b = 'eee'::text)Planning Time: 0.137 msExecution Time: 0.066 ms
(4 rows)

在某些情况下,当数据分布不均匀时(比如某列中有很多重复值,只有少数值是稀有的,比如上面例子中出现的b=‘eee’),即使考虑到重新生成执行计划的开销,自定义计划的执行成本仍然比通用计划低。这样,PostgreSQL 会优先选择使用自定义计划,而不会使用通用计划,甚至在重新规划的开销考虑进去之后,通用计划也可能永远不会被使用。

顺便提一下啊,在postgresql后,explain增加了generic_plan选型来供我们方便的查看通用执行计划

demo=# EXPLAIN (GENERIC_PLAN) SELECT * FROM t1 WHERE t1 = $1;QUERY PLAN
------------------------------------------------------------------------Gather  (cost=1000.00..31400.05 rows=15000 width=8)Workers Planned: 2->  Parallel Seq Scan on t1  (cost=0.00..28900.05 rows=6250 width=8)Filter: (t1.* = $1)
(4 rows)

总的来说,PostgreSQL 15 引入了 EXPLAIN (GENERIC_PLAN) 选项,使得开发者可以明确地查看通用计划,而不受参数变化的影响。

如果觉得文章有点价值,请帮忙点关注,谢谢

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

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

相关文章

dockfile 配置 /etc/apt/source.list.d/debian.list 清华镜像

docker:3.12.7 镜像使用的是 debian 系统&#xff0c;比 ubuntu 更轻量。debian 系统内&#xff0c;apt 镜像源列表位于 /etc/apt/source.list.d/debian.list&#xff08;作为对比&#xff0c;ubuntu 的镜像列表位于 /etc/apt/source.list&#xff0c;二者语法相同&#xff09;…

程序员测试日常小工具

作为一名程序员&#xff0c;或者测试人员&#xff0c;日常工作最常用的工具有哪些&#xff0c;截图&#xff0c;截图漂浮&#xff0c;翻译&#xff0c;日期处理&#xff0c;api调用...&#xff0c; 当你拿到一串报文后&#xff0c;想要json转换时&#xff0c;是不是要打…

【MySQL高级】第1-4章

第1章 存储过程 1.1 什么是存储过程&#xff1f; 存储过程可称为过程化SQL语言&#xff0c;是在普通SQL语句的基础上增加了编程语言的特点&#xff0c;把数据操作语句(DML)和查询语句(DQL)组织在过程化代码中&#xff0c;通过逻辑判断、循环等操作实现复杂计算的程序语言。 换…

深入浅出:AWT事件监听器及其应用

前言 在Java的GUI编程中&#xff0c;事件处理是非常重要的一环。AWT&#xff08;Abstract Window Toolkit&#xff09;框架提供了灵活的事件处理机制&#xff0c;使得开发者能够响应用户的操作&#xff0c;例如点击按钮、键盘输入、鼠标点击等。AWT的事件监听器就是实现这一机…

【Rust自学】8.5. HashMap Pt.1:HashMap的定义、创建、合并与访问

8.5.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构&#xff0c;这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。 第八章中的集合是存储在堆内存上而非栈内存上的&#xff0c;这也意味着这些集合的数据大小无需在编…

混合合并两个pdf文件

混合两个pdf 1、在线免费交替和混合奇数和偶数PDF页面2、有什么软件把两个 PDF 交叉合并&#xff1f;3、pdfsam本地合并 如何Google翻译的原文和译文合并&#xff0c;&#xff08;沉浸式翻译效果相对较好&#xff09; 1、在线免费交替和混合奇数和偶数PDF页面 https://deftpd…

Hutool 发送 HTTP 请求的几种常见写法

最简单的 GET 请求&#xff1a; String result HttpUtil.get("https://www.baidu.com");带参数的 GET 请求&#xff1a; // 方法1: 直接拼接URL参数 String result HttpUtil.get("https://www.baidu.com?name张三&age18");// 方法2: 使用 HashMap…

获取用户详细信息-ThreadLocal优化

Thread全局接口可用&#xff0c;不用再重复编写。所以为了代码的复用&#xff0c;使用Thread。把之前的内容&#xff08;函数的参数和map与username&#xff09;注释掉&#xff0c;换为Thread传过来的内容&#xff08;map与username&#xff09;。 因为Thread需要在拦截器里面…

THUCNews解压/THUCNews数据集解压出问题

省流&#xff1a;使用zip64进行解压&#xff0c;文件数目太多windows默认zip16装不下 我在使用THUCNews中文文本数据集时出现了问题&#xff0c;原数据集解压后应该包含以下两个文件夹: 其中THUCNews文件夹下有以新闻类别命名的子文件。官网下载的是一个1.56GB的zip压缩包 而我…

MySQL使用通用二进制文件安装到Unix/Linux

Oracle提供了一组MySQL的二进制发行版。其中包括用于许多平台的压缩tar文件&#xff08;扩展名为.tar.xz的文件&#xff09;形式的通用二进制发行版&#xff0c;以及用于选定平台的特定平台包格式的二进制文件。 本节介绍在Unix/Linux平台上从压缩的tar文件二进制分布安装MySQ…

安卓/system/bin下命令中文说明(AI)

ATFWD-daemon&#xff1a;AT指令转发守护进程&#xff0c;用于将AT指令从应用层转发到调制解调器。 PktRspTest&#xff1a;数据包响应测试工具。 StoreKeybox&#xff1a;存储密钥盒工具&#xff0c;用于安全地存储加密密钥。 WifiLogger_app&#xff1a;WiFi日志记录应用&…

Git操作总结

可以直接看实践 总结自施磊老师课程 Git与SVN对比 svn操作流程 写代码。 从服务器拉回服务器的当前版本库&#xff0c;并解决服务器版本库与本地代码的冲突。 将本地代码提交到服务器。 Git操作流程 写代码&#xff0c; 然后添加&#xff08;add&#xff09;到暂存区。 …

直流开关电源技术及应用二

文章目录 8 PFC8.1 基本概念8.1.1 功率因数8.1.2 功率因数偏低带来的影响8.1.3 特点 8.2 有源功率因数校正原理8.2.1不连续工作模式的矫正原理恒频控制技术控制目标控制关键要素控制过程实现方式公式Boost电路和boost pfc电路的联系和区别联系区别 恒导通时间控制 8.2.2 连续工…

UNI-APP_i18n国际化引入

官方文档&#xff1a;https://uniapp.dcloud.net.cn/tutorial/i18n.html vue2中使用 1. 新建文件 locale/index.js import en from ./en.json import zhHans from ./zh-Hans.json import zhHant from ./zh-Hant.json const messages {en,zh-Hans: zhHans,zh-Hant: zhHant }…

typora+picgo core+minio自动上传图片

1. 在服务器上安装docker版本minio 创建/docker/minio文件夹 mkdir -p /docker/minio在此文件夹创建docker-compose.yml version: "3.5" services:minio:image: quay.io/minio/minio:latestcontainer_name: minioprivileged: truerestart: alwaysports:# API接口访…

论文笔记:DepthLab: From Partial to Complete

是一篇很精炼的论文&#xff0c;不知道咋总结了&#xff0c;就差全文翻译了&#xff0c;不过在这里我主要关注3D部分&#xff0c;因为他的pipeline是基于SD的&#xff0c;框图也比较清晰易懂&#xff0c;非常细节的内容可以回头看论文&#xff0c;哈哈哈&#xff0c;给作者大佬…

LeetCode--排序算法(堆排序、归并排序、快速排序)

排序算法 归并排序算法思路代码时间复杂度 堆排序什么是堆&#xff1f;如何维护堆&#xff1f;如何建堆&#xff1f;堆排序时间复杂度 快速排序算法思想代码时间复杂度 归并排序 算法思路 归并排序算法有两个基本的操作&#xff0c;一个是分&#xff0c;也就是把原数组划分成…

ShardingSphere-Proxy分表场景:go测试案例

接续上篇文章《ShardingSphere-Proxy分表场景测试案例》 go测试用例&#xff1a; package mainimport ("fmt""math/rand""time""github.com/bwmarrin/snowflake""gorm.io/driver/mysql""gorm.io/gorm""gor…

主流在售AI电子宠物产品

市面上已经有许多类型的AI电子宠物产品&#xff0c;它们各具特色&#xff0c;旨在提供情感陪伴、教育娱乐以及智能互动等功能。以下是几款在市场上较为知名的AI电子宠物玩具&#xff0c;涵盖了不同的形态和技术特点&#xff1a; 1. Moflin 制造商&#xff1a;日本消费电子公司…

Debian-linux运维-docker安装和配置

腾讯云搭建docker官方文档&#xff1a;https://cloud.tencent.com/document/product/213/46000 阿里云安装Docker官方文档&#xff1a;https://help.aliyun.com/zh/ecs/use-cases/install-and-use-docker-on-a-linux-ecs-instance 天翼云常见docker源配置指导&#xff1a;htt…