postgresql-重复执行相同语句,试试 prepare!

文章目录

每次你向 PostgreSQL 发送 SQL 语句时,数据库都必须对其进行解析(parse)。解析虽然很快,但如果同样的语句被解析一千次,这种操作累积起来可能会占用大量时间,而这些时间本可以用于处理其他事务。为避免这种情况,PostgreSQL 提供了 prepare 语句。通过使用它,你可以避免重复解析语句,数据库只需执行planning和execution操作。

为了生成一些示例数据,这里使用了scale factor(规模因子)为 100 的 pgbench,这在 pgbench_accounts 表中产生10,000,000 行:

bash 复制代码
bench=#pgbench -U dbmgr -h 127.0.0.1 -p 5432 -i -s 100 bench
Password: 
dropping old tables...
NOTICE:  table "pgbench_accounts" does not exist, skipping
NOTICE:  table "pgbench_branches" does not exist, skipping
NOTICE:  table "pgbench_history" does not exist, skipping
NOTICE:  table "pgbench_tellers" does not exist, skipping
creating tables...
generating data (client-side)...
10000000 of 10000000 tuples (100%) done (elapsed 19.54 s, remaining 0.00 s)
vacuuming...
creating primary keys...
done in 29.01 s (drop tables 0.00 s, create tables 0.02 s, client-side generate 20.19 s, vacuum 0.70 s, primary keys 8.09 s).
bash 复制代码
postgres@pgrec-d:~psql bench
psql (15.5 (Ubuntu 15.5-1.pgdg22.04+1))
Type "help" for help.

bench=# select count(*) from pgbench_accounts;
  count  
----------
 10000000
(1 row)
 
bench=# d pgbench_accounts
              Table "public.pgbench_accounts"
  Column  |     Type      | Collation | Nullable | Default
----------+---------------+-----------+----------+---------
 aid      | integer       |           | not null | 
 bid      | integer       |           |          | 
 abalance | integer       |           |          | 
 filler   | character(84) |           |          | 
Indexes:
    "pgbench_accounts_pkey" PRIMARY KEY, btree (aid)

简单query一下:

bash 复制代码
bench=# select count(*) from pgbench_accounts where aid = 11111;
 count
-------
     1
(1 row)

正如本文开头所述,PostgreSQL 将需要解析该语句。使用带有正确选项的 explain,您可以看到产生执行计划花费了多少时间:

bash 复制代码
                                                                    QUERY PLAN                                                                     
---------------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=4.46..4.46 rows=1 width=8) (actual time=0.041..0.042 rows=1 loops=1)
   Buffers: shared hit=4
   ->  Index Only Scan using pgbench_accounts_pkey on pgbench_accounts  (cost=0.43..4.45 rows=1 width=0) (actual time=0.030..0.032 rows=1 loops=1)
         Index Cond: (aid = 11111)
         Heap Fetches: 0
         Buffers: shared hit=4
 Planning Time: 0.125 ms
 Execution Time: 0.086 ms
(8 rows)

产生此语句的执行计划比执行它花费更多时间。现在假设您要执行同一条语句一千次:

bash 复制代码
bench=#\t
bench=#select 'select count(*) from pgbench_accounts where aid = 11111;' from generate_series(1,1000) i; \g test.sql
bench=# \! cat test.sql | head
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
select count(*) from pgbench_accounts where aid = 11111;
...

当执行该命令时,强制 PostgreSQL解析所有这 1000 条语句:

bash 复制代码
bench=# \timing
Timing is on.
bench=#\! /usr/bin/time -p psql -U dbmgr -f test.sql -d bench
real 0.76s
user 0.13s
sys 0.1s

为了避免这种情况,我们使用prepare去准备这条sql:

bash 复制代码
prepare c1 as select count(*) from pgbench_accounts where aid = 11111;
PREPARE

一旦prepare好,就可以执行它:

bash 复制代码
bench=# execute c1;
 count
-------
     1
(1 row)

explain:

bash 复制代码
bench=# explain(analyze,buffers) execute c1;
 Aggregate  (cost=4.46..4.46 rows=1 width=8) (actual time=0.041..0.042 rows=1 loops=1)
   Buffers: shared hit=4
   ->  Index Only Scan using pgbench_accounts_pkey on pgbench_accounts  (cost=0.43..4.45 rows=1 width=0) (actual time=0.030..0.032 rows=1 loops=1)
         Index Cond: (aid = 11111)
         Heap Fetches: 0
         Buffers: shared hit=4
 Planning Time: 0.007 ms
 Execution Time: 0.100 ms

注意,与未准备好的语句相比,planning time减少了不少:

bash 复制代码
bench=# explain(analyze,buffers) select count(1) from pgbench_accounts where aid=11111;
 Aggregate  (cost=4.46..4.46 rows=1 width=8) (actual time=0.076..0.077 rows=1 loops=1)
   Buffers: shared hit=4
   ->  Index Only Scan using pgbench_accounts_pkey on pgbench_accounts  (cost=0.43..4.45 rows=1 width=0) (actual time=0.057..0.059 rows=1 loops=1)
         Index Cond: (aid = 11111)
         Heap Fetches: 0
         Buffers: shared hit=4
 Planning:
   Buffers: shared hit=3
 Planning Time: 0.376 ms
 Execution Time: 0.166 ms

当现在这样执行一千次:

bash 复制代码
bench=# \t
Tuples only is off.
bench=# select 'execute c1;' from generate_series(1,1000) i; \g test.sql
bench=# \! sed -i '1s/^/prepare c1 as select count(*) from pgbench_accounts where aid = 11111;/' test.sql
bench=# \! /usr/bin/time -p psql -U dbmgr -f test.sql -d bench
real 0.55s
user 0.11s
sys 0.15s

执行时间将会缩短。在这个简单的例子中,效果不太明显,但这是因为语句本身非常简单。顺便提一下,预编译的语句只在会话期间有效,所以 sed 命令将 prepare 语句添加到文件顶部,预编译本身也需要时间。如果不预编译,执行时间会更短。

当 where 子句中的值发生变化时,可以这样做:

bash 复制代码
bench=# prepare c1 as select count(*) from pgbench_accounts where aid = $1;
PREPARE
Time: 0.387 ms

解除prepare好的语句

bash 复制代码
bench=# deallocate c1;
DEALLOCATE
Time: 0.336 ms
相关推荐
silvamux_10 分钟前
10款模型写种草笔记:可发布性、返工来源与成本数据缺口复盘
java·数据库·笔记
I Am a robert girl2 小时前
从 GameHorizon 拆解多时间尺度游戏评测:数据管线、离线基准与在线回放的三层设计
数据库·游戏·多时间尺度·数据管线·游戏评测·离线基准·在线回放
躺柒2 小时前
读数据安全架构设计与实践17安全架构案例(下)
数据库·安全·零信任·安全架构·防火墙·无边界
SelectDB3 小时前
实时更新排错现场:导入了却查到旧数据,和 FINAL 越加越慢的两条路
大数据·数据库·数据分析
SelectDB3 小时前
拿 Planet 的账单一行行对下来,我总结出三个可自查的校验点
大数据·数据库·数据分析
SelectDB3 小时前
Apache Doris + Vane:让多模态数据进入业务分析
大数据·数据库·数据分析
AI开源软件4 小时前
SpringBoot信创项目怎么验收?从数据库迁移到交付证据
postgresql·springboot·达梦·opengauss·信创·企业管理系统
pjj198545 小时前
ClickHouse 与 Doris 数据库技术梳理:看懂分析型数据库
数据库·clickhouse
1024奇点5 小时前
【仓颉语言入门 · 第二篇 第 12 课】
开发语言·数据库·ide·mysql·开源
冰暮流星5 小时前
mysql之insert优化
数据库·mysql