sr_plan
概述
sr_plan类似Oracle Outline系统。它可以用来锁定执行计划。如果您不信任规划器或能够制定更好的计划,那本插件是必需的。
sr_plan插件可以保存QUERY的执行计划,(支持绑定变量的QUERY),同时允许篡改执行计划,让篡改的执行计划生效。
针对每一条保存的执行计划,允许单独开启或关闭。
sr_plan实际上通过post_parse_analyze_hook获取parser后的text并保存到sr_plan的query字段中,通过planner_hook保存、处理、返回保存的执行计划。
验证安装
testdb=# create extension sr_plan;
CREATE EXTENSION
用法示例
在需要使用sr_plan的数据库中创建extension, 会创建保留执行计划的表
testdb=# create extension sr_plan;
CREATE EXTENSION
testdb=# \d sr_plans
Table "public.sr_plans"
Column | Type | Collation | Nullable | Default
---------------+-------------------+-----------+----------+---------
query_hash | integer | | not null |
query_id | bigint | | not null |
plan_hash | integer | | not null |
enable | boolean | | not null |
query | character varying | | not null |
plan | text | | not null |
reloids | oid[] | | |
index_reloids | oid[] | | |
Indexes:
"sr_plans_query_hash_idx" btree (query_hash)
"sr_plans_query_index_oids" gin (index_reloids)
"sr_plans_query_oids" gin (reloids)
创建测试表,分别插入100万条记录
create table a(id int, info text);
create table b(id int, info text);
insert into a select generate_series(1,1000000), 'test'||generate_series(1,1000000); -- 插入100万数据
insert into b select * from a; -- 插入100万数据
create index idx_a_info on a (info);
create index idx_b_id on b(id);
开启sr_plan.write_mode, 允许sr_plan收集SQL和执行计划
testdb=# set sr_plan.write_mode = true;
SET
查看QUERY 1的执行计划
testdb=# explain (analyze,verbose,timing,costs,buffers) select * from a left join (select id,count(info) from b group by id) b on (a.id=b.id) where a.info='test1';
QUERY PLAN
--------------------------------------------------------------------------------
----------------------------------------------------------
Merge Left Join (cost=8.88..58897.90 rows=1 width=26) (actual time=2.412..2.83
2 rows=1 loops=1)
Output: a.id, a.info, b.id, (count(b.info))
Inner Unique: true
Merge Cond: (a.id = b.id)
Buffers: shared hit=2 read=6
-> Sort (cost=8.45..8.46 rows=1 width=14) (actual time=1.236..1.376 rows=1
loops=1)
Output: a.id, a.info
Sort Key: a.id
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=1 read=3
-> Index Scan using idx_a_info on public.a (cost=0.42..8.44 rows=1 wi
dth=14) (actual time=1.075..1.148 rows=1 loops=1)
Output: a.id, a.info
Index Cond: (a.info = 'test1'::text)
Buffers: shared hit=1 read=3
-> GroupAggregate (cost=0.42..46389.43 rows=1000000 width=12) (actual time=
0.986..1.091 rows=1 loops=1)
Output: b.id, count(b.info)
Group Key: b.id
Buffers: shared hit=1 read=3
-> Index Scan using idx_b_id on public.b (cost=0.42..31389.42 rows=10
00000 width=14) (actual time=0.695..0.770 rows=2 loops=1)
Output: b.id, b.info
Buffers: shared hit=1 read=3
Planning:
Buffers: shared hit=98 read=3 dirtied=15 written=6
Planning Time: 9.499 ms
Execution Time: 3.330 ms
(25 rows)
查看QUERY 2的执行计划
testdb=# explain (analyze,verbose,timing,costs,buffers) select * from a left join (select id,count(info) from b where exists (select 1 from a where a.id=b.id and a.info='test1') -- 改写QUERY,得到同样结果,但是B的聚合量减少了
group by id) b on (a.id=b.id) where a.info='test1';
QUERY PLAN
--------------------------------------------------------------------------------
-----------------------------------------------------------------------
Nested Loop Left Join (cost=17.34..25.40 rows=1 width=26) (actual time=1.337..
1.961 rows=1 loops=1)
Output: a.id, a.info, b.id, (count(b.info))
Inner Unique: true
Join Filter: (a.id = b.id)
Buffers: shared hit=12
-> Index Scan using idx_a_info on public.a (cost=0.42..8.44 rows=1 width=14
) (actual time=0.075..0.148 rows=1 loops=1)
Output: a.id, a.info
Index Cond: (a.info = 'test1'::text)
Buffers: shared hit=4
-> GroupAggregate (cost=16.92..16.94 rows=1 width=12) (actual time=1.144..1
.522 rows=1 loops=1)
Output: b.id, count(b.info)
Group Key: b.id
Buffers: shared hit=8
-> Sort (cost=16.92..16.92 rows=1 width=14) (actual time=0.988..1.333
rows=1 loops=1)
Output: b.id, b.info
Sort Key: b.id
Sort Method: quicksort Memory: 25kB
Buffers: shared hit=8
-> Nested Loop (cost=8.87..16.91 rows=1 width=14) (actual time=
0.389..1.106 rows=1 loops=1)
Output: b.id, b.info
Buffers: shared hit=8
-> HashAggregate (cost=8.45..8.46 rows=1 width=4) (actual
time=0.220..0.359 rows=1 loops=1)
Output: a_1.id
Group Key: a_1.id
Batches: 1 Memory Usage: 24kB
Buffers: shared hit=4
-> Index Scan using idx_a_info on public.a a_1 (cos
t=0.42..8.44 rows=1 width=4) (actual time=0.063..0.135 rows=1 loops=1)
Output: a_1.id, a_1.info
Index Cond: (a_1.info = 'test1'::text)
Buffers: shared hit=4
-> Index Scan using idx_b_id on public.b (cost=0.42..8.44
rows=1 width=14) (actual time=0.055..0.424 rows=1 loops=1)
Output: b.id, b.info
Index Cond: (b.id = a_1.id)
Buffers: shared hit=4
Planning:
Buffers: shared hit=27 read=2
Planning Time: 3.250 ms
Execution Time: 2.244 ms
(38 rows)
执行以下QUERY后,QUERY的执行计划被保存到sr_plans中
testdb=# select * from a left join (select id,count(info) from b group by id) b on (a.id=b.id) where a.info='test1';
id | info | id | count
----+-------+----+-------
1 | test1 | 1 | 1
(1 row)
testdb=# select * from a left join (select id,count(info) from b where exists (select 1 from a where a.id=b.id and a.info='test1') -- 改写QUERY,得到同样结果,但是B的聚合量减少了
group by id) b on (a.id=b.id) where a.info='test1';
id | info | id | count
----+-------+----+-------
1 | test1 | 1 | 1
(1 row)
禁止sr_plan收集SQL与执行计划
testdb=# set sr_plan.write_mode = false;
SET
查看保存的执行计划
testdb=# select query_hash,query from sr_plans ;
query_hash |
query
-------------+------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------
1208984763 | explain (analyze,verbose,timing,costs,buffers) select * from a le
ft join (select id,count(info) from b group by id) b on (a.id=b.id) where a.info
='test1';
-2097954535 | explain (analyze,verbose,timing,costs,buffers) select * from a le
ft join (select id,count(info) from b where exists (select 1 from a where a.id=b
.id and a.info='test1') +
| group by id) b on (a.id=b.id) where a.info='test1';
替换(篡改)执行计划将query_hash=1208984763的执行计划替换为-2097954535的执行计划达到query rewrite的目的
testdb=# update sr_plans set plan=(select plan from sr_plans where query_hash=-2097954535) where query_hash=1208984763;
UPDATE 1
允许QUERY使用sr_plan保存的执行计划
testdb=# update sr_plans set enable=true where query_hash=1208984763;
UPDATE 1
验证QUERY是否已使用sr_plan保存的执行计划
testdb=# \set VERBOSITY verbose
testdb=# explain select * from a left join (select id,count(info) from b group by id) b on (a.id=b.id) where a.info='test1';
QUERY PLAN
--------------------------------------------------------------------------------
---------
Merge Left Join (cost=8.88..58897.90 rows=1 width=26)
Merge Cond: (a.id = b.id)
-> Sort (cost=8.45..8.46 rows=1 width=14)
Sort Key: a.id
-> Index Scan using idx_a_info on a (cost=0.42..8.44 rows=1 width=14)
Index Cond: (info = 'test1'::text)
-> GroupAggregate (cost=0.42..46389.43 rows=1000000 width=12)
Group Key: b.id
-> Index Scan using idx_b_id on b (cost=0.42..31389.42 rows=1000000 w
idth=14)
(9 rows)
testdb=# select * from a left join (select id,count(info) from b group by id) b on (a.id=b.id) where a.info='test1';
id | info | id | count
----+-------+----+-------
1 | test1 | 1 | 1
(1 row)