1 背景
由于业务数据的变化 或者数据库版本的升级,可能导致SQL的执行计划发生变化,这种变化不一定是正收益,这时需 要一个防止计划劣化的机制。该机制需适用于版本升级时固化计划防止计划跳变等场景。
2 SPM 的功能
SPM(SQL Plan Manager)
功能:固化计划防止计划跳变,影响业务性能。
计划捕获:SPM能将一个具体SQL的执行计划落盘,称之为Plan baseline(计划基线) 。
计划选择:SPM会判断是否将优化器给出的执行计划交给执行器执行,选择优化器给出的执行计划,还是SPM存储的计划。
计划演进:SPM能将优化器新产生的计划进行判断,如果**判****断优秀会标记(ACC&UNACC)**以备计划选择使用。

3 SPM示例
步骤1:数据准备
csdn=> DROP TABLE IF EXISTS tb_a;
DROP TABLE
csdn=> CREATE TABLE tb_a (id int, c1 int, c2 int, pad text);
CREATE TABLE
csdn=> CREATE INDEX tb_a_idx_c1 ON tb_a (c1);
CREATE INDEX
csdn=> INSERT INTO tb_a select id, (random()*200)::int,(random()*10000)::int, 'ss' FROM (SELECT generate_series(1,10000) id) tb_a;
INSERT 0 10000
csdn=> ANALYZE tb_a;
ANALYZE
csdn=>
步骤2:参数设置
csdn=> SET spm_enable_plan_capture=manual; -- 开启SPM计划选择
SET
csdn=> SET spm_enable_plan_selection=on; -- 当前SPM只支持gplan,确保生成的计划是gplan
SET
csdn=> SET plan_cache_mode = 'force_generic_plan'; -- 在pretty模式可以看到baseline的使用情况
SET
csdn=>
csdn=> SET explain_perf_mode=pretty; --设置Oracle查看计划显示格风
SET
csdn=>
步骤3:计划捕获
-- 捕获tablescan,确保捕获tablescan计划
csdn=> SET enable_seqscan=on;
SET
csdn=>
csdn=> SET enable_indexscan=off;
SET
csdn=> SET enable_bitmapscan=off;
SET
csdn=>
-- 执行测试sql
csdn=> PREPARE spm_query AS SELECT * FROM tb_a WHERE c1 = $1;
PREPARE
csdn=> EXPLAIN(costs off) EXECUTE spm_query (1);
id | operation
----+----------------------
1 | -> Seq Scan on tb_a
(1 row)
Predicate Information (identified by plan id)
-----------------------------------------------
1 --Seq Scan on tb_a
Filter: (c1 = $1)
(2 rows)
csdn=>
csdn=> SELECT sql_hash, plan_hash, outline, status, gplan FROM gs_spm_sql_baseline WHERE sql_text LIKE '%tb_a WHERE c1 = $1%';
sql_hash | plan_hash | outline | status | gplan
------------+-----------+----------------------------------------+--------+-------
1850279601 | 154472964 | begin_outline_data +| ACC | t
| | TableScan(@"sel$1" csdn.tb_a@"sel$1")+| |
| | version("1.0.0") +| |
| | end_outline_data | |
(1 row)
csdn=>
步骤4:计划选择
csdn=> SET enable_bitmapscan=on; -- 确保优化器生成的计划是bitmapscan
SET
csdn=> SET enable_seqscan=off;
SET
csdn=> SET enable_indexscan=off;
SET
-- 执行SQL
csdn=> DEALLOCATE spm_query;
DEALLOCATE
csdn=> PREPARE spm_query AS SELECT * FROM tb_a WHERE c1 = $1;
PREPARE
csdn=> SET plan_cache_mode = 'force_generic_plan'; --强制走软解析
SET
csdn=> show plan_cache_mode;
plan_cache_mode
--------------------
force_generic_plan
(1 row)
csdn=> EXPLAIN(costs off) EXECUTE spm_query (1);
id | operation
----+--------------------------------------------
1 | -> Bitmap Heap Scan on tb_a
2 | -> Bitmap Index Scan using tb_a_idx_c1
(2 rows)
Predicate Information (identified by plan id)
-----------------------------------------------
1 --Bitmap Heap Scan on tb_a
Recheck Cond: (c1 = $1)
2 --Bitmap Index Scan using tb_a_idx_c1
Index Cond: (c1 = $1)
(4 rows)
====== Query Others =====
----------------------------------------------------------------
use_baseline: Yes, sql_hash: 3237163112, plan_hash: 2994191517
--查看现有计划
csdn=> SELECT sql_hash, plan_hash, outline, status, gplan, cost
csdn-> FROM gs_spm_sql_baseline
csdn-> WHERE sql_text like '%tb_a WHERE c1 = $1%'
csdn-> ORDER BY creation_time;
sql_hash | plan_hash | outline | status | gplan | cost
------------+------------+-----------------------------------------------------+--------+-------+--------
1850279601 | 154472964 | begin_outline_data +| ACC | t | 166
| | TableScan(@"sel$1" csdn.tb_a@"sel$1") +| | |
| | version("1.0.0") +| | |
| | end_outline_data | | |
3237163112 | 2994191517 | begin_outline_data +| ACC | t | 48.451
| | BitmapScan(@"sel$1" csdn.tb_a@"sel$1" tb_a_idx_c1)+| | |
| | version("1.0.0") +| | |
| | end_outline_data | | |
(2 rows)
csdn=>
注:从查看现有的计划,cost成本为48.451是正能量,对应的sql_hash、plan_hash分别为:
3237163112、 2994191517
步骤5:计划演进
我理解为将最优的计划打上ACC标签,不优的计划打上UNACC即可。
-- 使用spm 计划演进
csdn=> SELECT * FROM dbe_sql_util.gs_spm_evolute_plan(3237163112,2994191517);
evolute_status
----------------
t
(1 row)
csdn=> SELECT sql_hash, plan_hash, better, refer_plan, reason FROM gs_spm_sql_evolution WHERE sql_hash=3237163112;
sql_hash | plan_hash | better | refer_plan | reason
------------+------------+--------+------------+-----------------------------------------------------------
3237163112 | 2994191517 | f | 0 | execution time is more than 10% greater than the baseline
-- 根据演进结论修改seqscan计划状态为UNACC
csdn=> SELECT * FROM dbe_sql_util.gs_spm_set_plan_status (1850279601,154472964,'UNACC');
gs_spm_set_plan_status
------------------------
t
(1 row)
csdn=>
--查看UNACC
csdn=> SELECT sql_hash, plan_hash, outline, status, gplan, cost
FROM gs_spm_sql_baseline
WHERE sql_text like '%tb_a WHERE c1 = $1%'
ORDER BY creation_time;
sql_hash | plan_hash | outline | status | gplan | cost
------------+------------+-----------------------------------------------------+--------+-------+--------
1850279601 | 154472964 | begin_outline_data +| UNACC | t | 166
| | TableScan(@"sel$1" csdn.tb_a@"sel$1") +| | |
| | version("1.0.0") +| | |
| | end_outline_data | | |
3237163112 | 2994191517 | begin_outline_data +| ACC | t | 48.451
| | BitmapScan(@"sel$1" csdn.tb_a@"sel$1" tb_a_idx_c1)+| | |
| | version("1.0.0") +| | |
| | end_outline_data | | |
(2 rows)
csdn=>
步骤6:计划演证
csdn=> SET enable_seqscan=on;
SET
csdn=> SET enable_indexscan=on;
SET
csdn=> SET plan_cache_mode = 'force_generic_plan';
SET
csdn=> SET explain_perf_mode=pretty;
SET
csdn=> DEALLOCATE spm_query;
DEALLOCATE
csdn=> PREPARE spm_query AS SELECT * FROM tb_a WHERE c1 = $1;
PREPARE
csdn=> EXPLAIN(costs off) EXECUTE spm_query (1);
id | operation
----+--------------------------------------------
1 | -> Bitmap Heap Scan on tb_a
2 | -> Bitmap Index Scan using tb_a_idx_c1
(2 rows)
Predicate Information (identified by plan id)
-----------------------------------------------
1 --Bitmap Heap Scan on tb_a
Recheck Cond: (c1 = $1)
2 --Bitmap Index Scan using tb_a_idx_c1
Index Cond: (c1 = $1)
(4 rows)
4 批注
SPM主要是为要防止因为业务数据变化或版本升级引起的SQL计划跳变而影响业务性能。