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
相关推荐
付出不多23 分钟前
Linux——mysql主从复制与读写分离
数据库·mysql
初次见面我叫泰隆24 分钟前
MySQL——1、数据库基础
数据库·adb
Chasing__Dreams29 分钟前
Redis--基础知识点--26--过期删除策略 与 淘汰策略
数据库·redis·缓存
源码云商37 分钟前
【带文档】网上点餐系统 springboot + vue 全栈项目实战(源码+数据库+万字说明文档)
数据库·vue.js·spring boot
源远流长jerry1 小时前
MySQL的缓存策略
数据库·mysql·缓存
纯纯沙口1 小时前
Qt—用SQLite实现简单的注册登录界面
数据库·sqlite
初次见面我叫泰隆1 小时前
MySQL——3、数据类型
数据库·mysql
zxrhhm2 小时前
Oracle 中的虚拟列Virtual Columns和PostgreSQL Generated Columns生成列
postgresql·oracle·vr
一叶屋檐2 小时前
Neo4j 图书馆借阅系统知识图谱设计
服务器·数据库·cypher
好吃的肘子3 小时前
MongoDB 应用实战
大数据·开发语言·数据库·算法·mongodb·全文检索