執行計划存儲
如果同樣的SQL要執行很多遍,且每次都是同樣的執行計划、每次都發生硬解析,則會消耗大量時間。類似於Oracle存放執行計划的library cache,PG也有一個類似的概念——plan_cache。但實際上,PG提供的是預備語言(preparedstatement),它要求應用給這個語句進行標識后,再通過這個標識請求服務端執行,並且由應用負責回收。
對於無參的預備語句,在第一次執行的時候就會生成執行計划,之后會延用其來完成任務;對於有參的預備語句,最優執行計划會因為變量實際值的不同而不同。因此,在PG里:前5次執行預備語句,每一次都產生新的執行計划,叫做custom plan;第6次再執行時,會生成一個不依賴於參數的執行計划並保存下來,叫做generic plan。之后每執行一個預備語句,也都會產生一個相對應的custom plan。若generic plan比custom plan的平均值小1.1倍,則使用generic plan,否則使用當前產生的對應的custom plan。
-
custom plan是指對於preapre語句,在執行execute的時候,把execute語句中的參數嵌套到語句之后生成的計划。
-
custom plan會根據execute語句中具體的參數生成計划,這種方案的優點是每次都按照具體的參數生成優選計划,執行性能比較好;缺點是每次執行前都需要重新生成計划,存在大量的重復的優化器開銷。
-
generic plan是指對於preapre語句生成計划,該計划策略會在執行execute語句的時候把參數bind到plan中,然后執行計划。這種方案的優點是每次執行可以省去重復的優化器開銷;缺點是當bind參數字段上數據存在傾斜時該計划可能不是最優的, 部分bind參數場景下執行性能較差。
可以根據pg_prepared_statements視圖顯示當前會話所有可用的預備語句。
postgres=# \d pg_prepared_statements
View "pg_catalog.pg_prepared_statements"
Column | Type | Collation | Nullable | Default
-----------------+--------------------------+-----------+----------+---------
name | text | | |
statement | text | | |
prepare_time | timestamp with time zone | | |
parameter_types | regtype[] | | |
from_sql | boolean | | |
plan_cache_mode參數可以影響prepare語句選擇生成執行計划的策略。
-
auto表示按照默認的方式選擇custom plan或
者generic plan
-
force_generic_plan表示強制走generic plan
-
force_custom_plan表示強制走custom plan
此參數只對prepare語句生效,一般用在prepare語句中參數化字段存在比較嚴重數據傾斜的場景下。
通常情況,我們可以通過explain、explain analyze、explain verbose來獲取執行計划。
但是explain查詢當前緩存的執行計划,在實際中估算的成本可能是不准確的,因為很可能估算的成本和實際運行的成本不一致。而explain analyze、explain verbose則會實際執行SQL,但在某些場景不會被允許執行。
可以嘗試采用開啟一個事務后,explain analyze、explain verbose查看執行計划,最后rollback。
pg_show_plans模塊
接下來的主題則是一個供PostgreSQL數據庫查詢正在進行的SQL執行計划的模塊——pg_show_plans,它可以動態查找當前正在進行的SQL執行計划。
pg_show_plans是一個顯示當前運行的所有SQL語句執行計划的模塊。它在plan結束位置,截獲並存儲當前plan tree,從而使其他會話可以打印存儲plan tree。此模塊支持從9.5到12的PostgreSQL版本。它會在共享內存上創建一個哈希表,以便臨時存儲查詢計划。哈希表大小不能更改,因此如果哈希表已滿,則不會存儲計划。
安裝及使用介紹
1.進到數據庫對應的contrib目錄下。
[postgres@t1ysl opt]$ cd /opt/postgresql-12.1/contrib/
2.獲取pg_show_plans擴展包。
[postgres@t1ysl contrib]$ git clone https://github.com/cybertec-postgresql/pg_show_plans.git
Cloning into 'pg_show_plans'...
remote: Enumerating objects: 70, done.
remote: Counting objects: 100% (2/2), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 70 (delta 0), reused 0 (delta 0), pack-reused 68
Unpacking objects: 100% (70/70), done.
3.編譯安裝。
[postgres@t1ysl contrib]$ cd pg_show_plans/
[postgres@t1ysl pg_show_plans]$ make
make -C ../../src/backend generated-headers
make[1]: Entering directory `/opt/postgresql-12.1/src/backend'
make -C catalog distprep generated-header-symlinks
make[2]: Entering directory `/opt/postgresql-12.1/src/backend/catalog'
make[2]: Nothing to be done for `distprep'.
make[2]: Nothing to be done for `generated-header-symlinks'.
make[2]: Leaving directory `/opt/postgresql-12.1/src/backend/catalog'
make -C utils distprep generated-header-symlinks
make[2]: Entering directory `/opt/postgresql-12.1/src/backend/utils'
make[2]: Nothing to be done for `distprep'.
make[2]: Nothing to be done for `generated-header-symlinks'.
make[2]: Leaving directory `/opt/postgresql-12.1/src/backend/utils'
make[1]: Leaving directory `/opt/postgresql-12.1/src/backend'
gcc -std=gnu99 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -O2 -fPIC -I. -I. -I../../src/include -D_GNU_SOURCE -c -o pg_show_plans.o pg_show_plans.c
gcc -std=gnu99 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -O2 -fPIC -shared -o pg_show_plans.so pg_show_plans.o -L../../src/port -L../../src/common -Wl,--as-needed -Wl,-rpath,'/opt/pg12/lib',--enable-new-dtags
[postgres@t1ysl pg_show_plans]$ make install
make -C ../../src/backend generated-headers
make[1]: Entering directory `/opt/postgresql-12.1/src/backend'
make -C catalog distprep generated-header-symlinks
make[2]: Entering directory `/opt/postgresql-12.1/src/backend/catalog'
make[2]: Nothing to be done for `distprep'.
make[2]: Nothing to be done for `generated-header-symlinks'.
make[2]: Leaving directory `/opt/postgresql-12.1/src/backend/catalog'
make -C utils distprep generated-header-symlinks
make[2]: Entering directory `/opt/postgresql-12.1/src/backend/utils'
make[2]: Nothing to be done for `distprep'.
make[2]: Nothing to be done for `generated-header-symlinks'.
make[2]: Leaving directory `/opt/postgresql-12.1/src/backend/utils'
make[1]: Leaving directory `/opt/postgresql-12.1/src/backend'
/bin/mkdir -p '/opt/pg12/lib/postgresql'
/bin/mkdir -p '/opt/pg12/share/postgresql/extension'
/bin/mkdir -p '/opt/pg12/share/postgresql/extension'
/bin/install -c -m 755 pg_show_plans.so '/opt/pg12/lib/postgresql/pg_show_plans.so'
/bin/install -c -m 644 ./pg_show_plans.control '/opt/pg12/share/postgresql/extension/'
/bin/install -c -m 644 ./pg_show_plans--1.0.sql '/opt/pg12/share/postgresql/extension/'
4.在postgresql.conf文件的shared_preload_libraries里增加pg_show_plans,並重啟數據庫生效。
vi postgresql.conf
增加 shared_preload_libraries = 'pg_show_plans'
[postgres@t1ysl ~]$ pg_ctl restart
waiting for server to shut down.... done
server stopped
waiting for server to start....2021-07-25 08:52:08.402 CST [2990] LOG: starting PostgreSQL 12.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44), 64-bit
2021-07-25 08:52:08.402 CST [2990] LOG: listening on IPv4 address "0.0.0.0", port 6000
2021-07-25 08:52:08.402 CST [2990] LOG: listening on IPv6 address "::", port 6000
2021-07-25 08:52:08.406 CST [2990] LOG: listening on Unix socket "/tmp/.s.PGSQL.6000"
2021-07-25 08:52:08.434 CST [2990] LOG: redirecting log output to logging collector process
2021-07-25 08:52:08.434 CST [2990] HINT: Future log output will appear in directory "/opt/pg_log6000".
done
server started
5.創建EXTENSION。
postgres=# CREATE EXTENSION pg_show_plans;
CREATE EXTENSION
6.通過pg_show_plans表可查看當前正在進行的SQL執行計划。
postgres=# \d pg_show_plans
View "public.pg_show_plans"
Column | Type | Collation | Nullable | Default
--------+--------+-----------+----------+---------
pid | bigint | | |
level | bigint | | |
userid | oid | | |
dbid | oid | | |
plan | text | | |
模擬使用場景
1.開啟兩個session。
-
一個session執行一條較慢SQL(便於獲取到其執行計划)
-
一個session在SQL執行過程獲取其執行計划
2.這里我舉例的SQL為對一張346MB的表的全表掃描。
session1:
postgres=# \dt+ t1_ysl
List of relations
Schema | Name | Type | Owner | Size | Description
--------+--------+-------+----------+--------+-------------
public | t1_ysl | table | postgres | 346 MB |
(1 row)
postgres=# select * from t1_ysl ;
id
---------
3511203
5877715
7284053
4522491
3815961
6454179
2712063
...
通過pg_show_plans和pg_stat_activity聯合查詢出當前正在進行的SQL執行計划。
session2:
postgres=# SELECT * FROM pg_show_plans;
pid | level | userid | dbid | plan
------+-------+--------+-------+------------------------------------------------------------------
-----
1812 | 0 | 10 | 13593 | Function Scan on pg_show_plans (cost=0.00..10.00 rows=1000 width
=56)
1899 | 0 | 10 | 13593 | Seq Scan on t1_ysl (cost=0.00..144247.77 rows=9999977 width=4)
(2 rows)
postgres=# SELECT p.pid, p.level, p.plan, a.query FROM pg_show_plans p
LEFT JOIN pg_stat_activity a
ON p.pid = a.pid AND p.level = 0 ORDER BY p.pid, p.level;
pid | level | plan
| query
------+-------+-----------------------------------------------------------------------------------
-------------+----------------------------------------------------------------
1812 | 0 | Sort (cost=72.08..74.58 rows=1000 width=80)
+| SELECT p.pid, p.level, p.plan, a.query FROM pg_show_plans p +
| | Sort Key: pg_show_plans.pid, pg_show_plans.level
+| LEFT JOIN pg_stat_activity a +
| | -> Hash Left Join (cost=2.25..22.25 rows=1000 width=80)
+| ON p.pid = a.pid AND p.level = 0 ORDER BY p.pid, p.level;
| | Hash Cond: (pg_show_plans.pid = s.pid)
+|
| | Join Filter: (pg_show_plans.level = 0)
+|
| | -> Function Scan on pg_show_plans (cost=0.00..10.00 rows=1000 width=48)
+|
| | -> Hash (cost=1.00..1.00 rows=100 width=44)
+|
| | -> Function Scan on pg_stat_get_activity s (cost=0.00..1.00 rows=1
00 width=44) |
1899 | 0 | Seq Scan on t1_ysl (cost=0.00..144247.77 rows=9999977 width=4)
| select * from t1_ysl ;
(2 rows)
相關參數
-
pg_show_plans.enable 是否可以顯示計划。
-
pg_show_plans.plan_format 它控制查詢計划的輸出格式。可以選擇文本或JSON。默認為文本。
-
pg_show_plans.max_plan_length 它設置查詢計划的最大長度。默認值為8192[字節]。此參數必須設置為整數
轉載自數據和雲公眾號