AnalyticDB PostgreSQL版通过sr_plan插件提供了查询计划管理(SQL Plan Management)功能,本文为您介绍如何使用查询计划管理功能。
注意事项
仅内核版本为V6.3.9.0及以上的AnalyticDB PostgreSQL版存储弹性模式实例支持查询计划管理功能。
V6.3.8.9及以后版本,安装或升级插件需要提交工单联系技术支持进行处理。
功能介绍
AnalyticDB PostgreSQL版优化器基于统计数据而非固定的规则,系统会评估SQL语句各种可行的执行算子的代价并选择代价最低的组合执行。虽然优化器会尽可能选择最优的执行计划,但是在复杂的业务场景中,急剧变化的数据量与数据分布特征也会导致执行计划的变动过大,同时,优化器版本的升级与特性的增强也会导致计划变化,偶尔会产生不符合预期的执行计划。在分析场景中,偶尔出现周期性相对复杂的报表查询,优化器需要在较大的查询计划空间中检索出相对最佳的计划,检索耗时较长。
查询计划管理功能可以存储指定查询语句和查询计划,当出现同类查询语句时,可以直接调用存储的查询计划。查询计划管理功能具有如下优点:
减少重复生成查询计划的时间。
稳定查询计划,避免在数据量大且数据变更频繁的场景下查询计划变动过大。
支持参数化查询语句中的常数,一个查询计划可以适用于所有常数不同的同类查询语句。
管理插件
安装插件
使用查询计划管理功能,您需要首先安装sr_plan插件,语句如下:
CREATE EXTENSION sr_plan;
仅支持在安装插件的库中使用该功能。
临时关闭
如果需要在当前会话中临时关闭查询计划管理功能,可以通过如下语句关闭:
SET sr_plan.enabled to off;
卸载插件
如果不需要使用查询计划管理功能,可以通过如下语句卸载插件:
DROP EXTENSION sr_plan;
sr_plans表
安装sr_plan插件后,系统会生成一张sr_plans表用于存储计划,每个计划对应一条同类查询语句和查询计划。表结构如下:
列名 | 类型 | 说明 |
query_hash | bigint | 查询语句参数化后的64位Hash值,用于标识同类查询语句。 |
query_id | int8 | 预留值。 |
plan_hash | bigint | 查询计划序列化后的64位Hash值,用于标识查询计划。 |
enable | bool | 查询计划是否生效,取值如下:
|
query | varchar | 注册时的查询语句。 |
plan | bytea | 查询计划序列化后的二进制序列,可以使用show_plan或show_plan_node函数展示详细内容。函数的使用方法,请参见易用性函数使用说明。 |
const_list | bytea | 参数化列表序列化后的二进制序列,可以使用show_const_list函数展示详细内容。函数的使用方法,请参见易用性函数使用说明。 |
reloids | oid[] | 查询计划相关的表的OID列表。 说明 当查询计划相关的表被删除时,该计划也会被删除。 |
index_reloids | oid[] | 查询计划相关的索引的OID列表。 说明 当查询计划相关的索引被删除时,该计划也会被删除。 |
GUC参数
AnalyticDB PostgreSQL版提供了如下GUC参数控制查询计划管理功能:
参数名称 | 默认值 | 说明 |
sr_plan.enabled | on | 是否开启查询计划管理功能,取值如下:
|
sr_plan.log_usage | none | sr_plan详细使用信息的日志等级,取值如下: warning、notice、info、log、debug[1-5]、none。 none表示不记录。 |
sr_plan.write_mode | off | 是否进入存储计划的模式,取值如下:
说明 该参数不建议手动修改,如需存储计划请使用注册计划的函数,具体操作,请参见易用性函数使用说明。 |
易用性函数使用说明
注册计划
bool sr_plans_register(<query>, <const_list>, <hint_str>)
参数说明如下:
参数 | 类型 | 说明 |
query | text | 查询语句,支持如下两种模式:
具体匹配请以query_hash参数为准,建议开启 |
const_list | text | 填充execute时的参数列表。 仅query为prepare模式时,需要传入const_list参数。默认为NULL。 例如: |
hint_str | text | 需要额外添加的Hint字符串。Hint使用信息,请参见使用Hint。 默认为 |
如果发现已注册的查询语句未使用已注册的执行计划,可能是因为数据类型的强一致匹配问题。您可以使用EXPLAIN获取常数在语句中的实际数据类型,通过显式类型转换进行注册。示例如下:
_p模式:
rname = 'ASIA'
显式转换为rname = _p('ASIA'::bpchar)
。prepare模式:
a = 1
显式转换为a = $1::int
。
示例
使用_p模式注册计划,示例如下:
SELECT sr_plans_register('SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = _p(11);', NULL, '/*+ Set(optimizer_enable_hashjoin off) */');
使用prepare模式注册计划,示例如下:
SELECT sr_plans_register('SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = $1;', '11', '/*+ MergeJoin(test_table test_table2) Leading((test_table test_table2)) */');
如果sr_plans_register函数参数报错导致内部prepare对象没有释放,可以使用clean_sr_plans_register_prepare()
函数进行清理。
查看计划
查看文本计划
show_plan(<query_hash>,<plan_hash>,<format>)
参数
类型
说明
query_hash
bigint
查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。
plan_hash
bigint
查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。
默认为NULL,为NULL时表示第一个enable状态的记录。
format
text
展示格式,默认为
'text'
。取值如下:'text'
'xml'
'json'
'yaml'
示例
查看指定文本计划,返回结果的展示格式为
'text'
,示例如下:SELECT show_plan(-7846983602634689470, 1283098916874729409, 'text');
查看完整计划结构
show_plan_node(<query_hash>,<plan_hash>,<pretty>)
参数
类型
说明
query_hash
bigint
查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。
plan_hash
bigint
查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。
默认为NULL,为NULL时表示第一个enable状态的记录。
pretty
bool
是否使用换行和缩进的格式展示结果。取值如下:
true(默认):使用换行和缩进的展示格式。
false:不使用换行和缩进的展示格式。
示例
查看完整的计划结构,返回结果自动换行和缩进,示例如下:
SELECT show_plan_node(-7846983602634689470, 1283098916874729409, on);
查看参数化列表
show_const_list(<query_hash>,<plan_hash>,<is_list>,<pretty>)
参数
类型
说明
query_hash
bigint
查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。
plan_hash
bigint
查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。
默认为NULL,为NULL时表示第一个enable状态的记录。
is_list
bool
是否展示完整的算子结构,取值如下:
true(默认):展示完整的算子列表结构。
false:展示简洁的字符串列表。
pretty
bool
是否使用换行和缩进的格式展示结果。取值如下:
true(默认):使用换行和缩进的展示格式。
false:不使用换行和缩进的展示格式。
示例
查看参数化列表,返回结果展示完整的算子列表结构,不使用换行和缩进的展示效果,示例如下:
SELECT show_const_list(-7846983602634689470, 1283098916874729409, true, false);
启用计划
enable_sr_plans(<query_hash>,<plan_hash>)
参数 | 类型 | 说明 |
query_hash | bigint | 查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。 |
plan_hash | bigint | 查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。 默认为NULL,为NULL时表示所有对应query_hash的plan_hash的记录。 |
示例
启用指定的计划,示例如下:
SELECT enable_sr_plans(-7846983602634689470, 1283098916874729409);
停止计划
disable_sr_plans(<query_hash>,<plan_hash>)
参数 | 类型 | 说明 |
query_hash | bigint | 查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。 |
plan_hash | bigint | 查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。 默认为NULL,为NULL时表示所有对应query_hash的plan_hash的记录。 |
示例
停止指定的计划,示例如下:
SELECT disable_sr_plans(-7846983602634689470, 1283098916874729409);
删除计划
delete_sr_plans(<query_hash>,<plan_hash>)
参数 | 类型 | 说明 |
query_hash | bigint | 查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。 |
plan_hash | bigint | 查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。 默认为NULL,为NULL时表示所有对应query_hash的plan_hash的记录。 |
示例
删除指定的计划,示例如下:
SELECT delete_sr_plans(-7846983602634689470, 1283098916874729409);
修改计划
update_sr_plans(<query_hash>,<plan_node_string>,<plan_hash>)
该函数处理过程中仅校验字符串是否满足读取并转化为对应的可执行计划的要求,不校验对应计划是否完全满足该查询的要求。建议您在对Plan的数据结构有较高的理解的情况下使用。
参数 | 类型 | 说明 |
query_hash | bigint | 查询语句参数化后的64位Hash值,对应sr_plans表中的query_hash值。 |
plan_node_string | text | 修改后的plan node的字符串。可以使用show_plan_node函数可获取当前状态。 |
plan_hash | bigint | 查询计划参数化后的64位Hash值,对应sr_plans表中的plan_hash值。 默认为NULL,为NULL时表示所有对应query_hash的plan_hash的记录。 |
使用示例
准备测试用表test_table和test_table2,并插入测试数据。
DROP TABLE IF EXISTS test_table; DROP TABLE IF EXISTS test_table2; CREATE TABLE test_table(test_attr1 int, test_attr2 int); CREATE TABLE test_table2(test_attr1 int, test_attr2 int); INSERT INTO test_table SELECT i, i + 1 FROM generate_series(1, 20000) i; INSERT INTO test_table2 SELECT i, i + 1 FROM generate_series(1, 20000) i; CREATE INDEX test_table_index1 ON test_table (test_attr1); CREATE INDEX test_table_index2 ON test_table (test_attr2); ANALYZE test_table; ANALYZE test_table2;
开启sr_plan日志显示,用于查看注册和使用时的提示。
建议您在注册时启用,便于观测是否可以使用保存的计划。
SET sr_plan.log_usage = NOTICE;
查看原生查询计划。
执行查询计划:
EXPLAIN SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = 11;
返回信息如下:
QUERY PLAN ------------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..438.43 rows=1 width=16) -> Hash Join (cost=0.00..438.43 rows=1 width=16) Hash Cond: (test_table2.test_attr1 = test_table.test_attr1) -> Seq Scan on test_table2 (cost=0.00..431.14 rows=6667 width=8) -> Hash (cost=6.00..6.00 rows=1 width=8) -> Index Scan using test_table_index2 on test_table (cost=0.00..6.00 rows=1 width=8) Index Cond: (test_attr2 = 11) Optimizer: Pivotal Optimizer (GPORCA) version 3.86.0 (8 rows)
注册查询计划。
使用_p模式,带Hint:
SELECT sr_plans_register('SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = _p(11);', NULL, '/*+ Set(optimizer_enable_hashjoin off) */');
返回信息如下:
NOTICE: sr_plan: saved plan for /*+ Set(optimizer_enable_hashjoin off) */SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = _p(11); CONTEXT: SQL statement "/*+ Set(optimizer_enable_hashjoin off) */SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = _p(11);" PL/pgSQL function sr_plans_register(text,text,text) line 17 at EXECUTE statement corresponding column is : sr_plans_register sr_plans_register ------------------- t (1 row)
使用prepare模式,无法使用ORCA优化器,带Hint:
SELECT sr_plans_register('SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = $1;', '11', '/*+ MergeJoin(test_table test_table2) Leading((test_table test_table2)) */');
返回信息如下:
NOTICE: sr_plan: saved plan for select * from test_table, test_table2 where test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = $1; CONTEXT: SQL statement "/*+ MergeJoin(test_table test_table2) Leading((test_table test_table2)) */execute _sr_plans_register(11)" PL/pgSQL function sr_plans_register(text,text,text) line 27 at EXECUTE statement corresponding column is : sr_plans_register sr_plans_register ------------------- t (1 row)
查询所有记录的计划。
SELECT * FROM sr_plans;
为了更好地展示效果,您可以在psql中执行
\x
命令来横向展示结果,返回信息如下:-[ RECORD 1 ]-+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- query_hash | -7846983602634689470 query_id | 0 plan_hash | 1283098916874729409 enable | f query | /*+ Set(optimizer_enable_hashjoin off) */SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = _p(11); plan | \x28b52ffd603f07150d00520b2a31504f0200745b6bf119296929280e2ae3d08e676124f9ca5fc36c629033c7b9a7a9bf766e213a3e4266ce009604c2ffbef32823871cd54a100b835171815211be6dfaa6d4ad563800a7f48bdc2a744c5dc8a4feeba7284366093db00c71a02190f870675a5b137a269a8d96868e83c5f4b733c6b21ecfc0752250e0e15cab5a4b2b33395506c20b063530f08f32c618ff808f4a133af1bbcca857f2dee7c220a5c311d1ecef4d928080a8b0d64000082000f0829199e1361200c234ca2508e40831901063483011c944125018292ceda0be8ff48b9cc45e2e1df50755b915267643696e998066ca9cdbdaae59e2e74e6a40f2efd406378e2636c854b8508978e1ecf58b356ea8bdab1b208ed06a4c2dea383dc365f24e0ed8e4a78ac542b50cf462aecf05650c56bab6f4266ab87c29354a59b2509a3fed448f897e4ccdf0623e9f293f6dbff7773e405f0884b67d8bc8c18f245c4b8a82297074668c19e8b0158d9ecdd3986fa2adddc4ed0cbaad8a1f54de36785a99cc39475c06e36f415872b3801e3a45a10aa91eded2dcddd3cf88abbd9824b0259f271a8692919907 const_list | \x28b52ffd2049ad0100540290020100000035010f80000017003001ffffffff040000000100a30000000ba0000000adde05004f9107417606fc3904325002 reloids | {40963,40960,40960,40963} index_reloids | {40969} -[ RECORD 2 ]-+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- query_hash | -7846983602634689470 query_id | 0 plan_hash | 8380868479165711144 enable | f query | select * from test_table, test_table2 where test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = $1; plan | \x28b52ffd604a09c51000024f383730918200d5a047fa8799c1e341cc05c54480f746007c5f533b0d22256706d75b93bfed805f83bc0a6319ff43a73f4fbf55a3ed96bef73d35272fde92a40102d5171724a5427da08a5ebe361594ce16b5abf26cb13b631f23b0f98a19b4546c293fc0ac6fddf58b68f773dce8e8f337fc13aa2072a113828e4bc8a9041323b3da5577c38012ce0451cc700c172513f439a0146cd8cc7a5b69fd10a1473da9ad4c4a59c752bdd7344d76524ad2ee8add0a043f0fc448813f171ac771fc0f76663a52895fdbf033452e93800649c68479dc2c3c98bef9caa189784930d1c3755902809fa8d1d522400109822001f00281111a2b1200932cc9640c42c000048c314a0015000413418080090c680cc6838e2fd492d1b489b1738684cc0313c3b91659002bb5237c70b22b863bcc54a973f15fe134370df65c634396bf878f89b3658422ac27a9bb02c7d8b83cd81d1f074acaf5a107f14aa5a1d6c9d5c03b4aa7b08ad04359d3fe16bd509d41c12e0c028c55c9a0390794423566ac01d18c7b021016bf8ba100e83144bf84b0e4ad418a313c57df176b143aab601c43cd46c9516f08c7ea00e3d040d35e7d8b50d52462dfd3b03662d2674a78c9e9cc825594d3fbed29f0acdd1ce9caabbeae8a34c54a23879edea532081ef80aac28e26c1e26e6d99915549bcbb8a7d82cf8c2fef988a1c623472cff31c25dc69a69a130bdac655748e0a6ac3abe97d95b431fe104caffe6c083f007 const_list | \x28b52ffd2020010100900201000000310100000100000017000000ffffffff0000000095000000adde reloids | {40960,40963} index_reloids | {40970}
查看计划。
通过query_hash和plan_hash字段查看第一个计划:
SELECT show_plan(-7846983602634689470, 1283098916874729409);
返回信息如下:
show_plan ------------------------------------------------------------------------------------------------------------------ ("Gather Motion 3:1 (slice1; segments: 3)") (" Output: test_table.test_attr1, test_table.test_attr2, test_table2.test_attr1, test_table2.test_attr2") (" -> Nested Loop") (" Output: test_table.test_attr1, test_table.test_attr2, test_table2.test_attr1, test_table2.test_attr2") (" Join Filter: true") (" -> Seq Scan on public.test_table2") (" Output: test_table2.test_attr1, test_table2.test_attr2") (" -> Index Scan using test_table_index1 on public.test_table") (" Output: test_table.test_attr1, test_table.test_attr2") (" Index Cond: (test_table.test_attr1 = test_table2.test_attr1)") (" Filter: (test_table.test_attr2 = _p(11))") ("Optimizer: Pivotal Optimizer (GPORCA) version 3.86.0") ("Settings: rds_plan_cache_mode=auto") (13 rows)
通过query_hash和plan_hash字段查看第一个计划的参数列表:
SELECT show_const_list(-7846983602634689470, 1283098916874729409, false);
返回信息如下:
show_const_list ---------------------------- _p(4 [ 11 0 0 0 0 0 0 0 ]) (1 row)
通过query_hash和plan_hash字段查看第二个计划:
SELECT show_plan(-7846983602634689470, 8380868479165711144);
返回信息如下:
show_plan ------------------------------------------------------------------------------------------------------------------ ("Gather Motion 3:1 (slice1; segments: 3)") (" Output: test_table.test_attr1, test_table.test_attr2, test_table2.test_attr1, test_table2.test_attr2") (" -> Merge Join") (" Output: test_table.test_attr1, test_table.test_attr2, test_table2.test_attr1, test_table2.test_attr2") (" Merge Cond: (test_table.test_attr1 = test_table2.test_attr1)") (" -> Sort") (" Output: test_table.test_attr1, test_table.test_attr2") (" Sort Key: test_table.test_attr1") (" -> Index Scan using test_table_index2 on public.test_table") (" Output: test_table.test_attr1, test_table.test_attr2") (" Index Cond: (test_table.test_attr2 = $1)") (" -> Sort") (" Output: test_table2.test_attr1, test_table2.test_attr2") (" Sort Key: test_table2.test_attr1") (" -> Seq Scan on public.test_table2") (" Output: test_table2.test_attr1, test_table2.test_attr2") ("Optimizer: Postgres query optimizer") ("Settings: rds_plan_cache_mode=auto") (18 rows)
通过query_hash和plan_hash字段查看第二个计划的参数列表:
SELECT show_const_list(-7846983602634689470, 8380868479165711144, false);
返回信息如下:
show_const_list ----------------- $1 (1 row)
使用查询计划管理功能。
验证第一个计划
启用第一个计划:
SELECT enable_sr_plans(-7846983602634689470, 1283098916874729409);
在启用第一个计划的情况下执行查询计划,参数化常数可变:
EXPLAIN SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = 12;
返回信息如下:
NOTICE: sr_plan: cached plan was used for query: explain SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = 12; QUERY PLAN ----------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..120434.24 rows=8000 width=16) -> Nested Loop (cost=0.00..120433.77 rows=2667 width=16) Join Filter: true -> Seq Scan on test_table2 (cost=0.00..431.14 rows=6667 width=8) -> Index Scan using test_table_index1 on test_table (cost=0.00..120002.35 rows=1 width=8) Index Cond: (test_attr1 = test_table2.test_attr1) Filter: (test_attr2 = 12) Optimizer: Pivotal Optimizer (GPORCA) version 3.86.0 (8 rows)
停止第一个计划:
SELECT disable_sr_plans(-7846983602634689470, 1283098916874729409);
验证第二个计划
启用第二个计划:
SELECT enable_sr_plans(-7846983602634689470, 8380868479165711144);
在启用第二个计划的情况下执行查询计划,参数化常数可变:
EXPLAIN SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = 13;
返回信息如下:
NOTICE: sr_plan: cached plan was used for query: explain SELECT * FROM test_table, test_table2 WHERE test_table.test_attr1 = test_table2.test_attr1 and test_table.test_attr2 = 13; QUERY PLAN ------------------------------------------------------------------------------------------------------ Gather Motion 3:1 (slice1; segments: 3) (cost=1660.96..1760.98 rows=15 width=16) -> Merge Join (cost=1660.96..1760.98 rows=5 width=16) Merge Cond: (test_table.test_attr1 = test_table2.test_attr1) -> Sort (cost=8.19..8.20 rows=1 width=8) Sort Key: test_table.test_attr1 -> Index Scan using test_table_index2 on test_table (cost=0.16..8.18 rows=1 width=8) Index Cond: (test_attr2 = 13) -> Sort (cost=1652.77..1702.77 rows=6667 width=8) Sort Key: test_table2.test_attr1 -> Seq Scan on test_table2 (cost=0.00..224.00 rows=6667 width=8) Optimizer: Postgres query optimizer (11 rows)
停止第二个计划:
SELECT disable_sr_plans(-7846983602634689470, 8380868479165711144);
删除计划。
删除第一个计划:
SELECT delete_sr_plans(-7846983602634689470, 1283098916874729409);
删除第二个计划:
SELECT delete_sr_plans(-7846983602634689470, 8380868479165711144);