作者:nigel bayliss
sql plan management(spm)被设计用于预防应用中使用的sql(假设问题sql会被使用超过1次)发生性能退化。spm使用与单个sql相关联的sql plan baselines来控制哪些执行计划是允许使用的。这是一个简单但功能强大的想法,它为以更多选择性和应对方法来使用sql plan baselines的可能性,打开了一扇大门:在不修改应用查询或者不改变应用本身的情况下,影响单个查询的执行计划。在博客集(点此链接)和spm白页()中涵盖了相关技术介绍,但是,用一个完整示例来展示它,还是值得的 。
如果您不想阅读下面的所有背景并跳转到一个真实的例子,我已经在github中添加了一些新的脚本()。它们类似于oracle支持部门几年前发布的sql概要文件示例,它们演示了如何从sql调优集和awr中检索计划。它们将在oracle database 12c release 2以后的版本(甚至在oracle database 18c 标准版上)中工作。下面的另一个代码示例则可以在oracle database 11g以后的版本中工作。
考虑这样一个场景:一个应用程序使用了一个sql语句,该应用程序有一个次优计划,您需要对此做些什么。为了便于讨论,我们假设您知道有一个提示可以用来实现一个更好的计划。从现在开始,我假设您希望应用一个提示,但是应用程序代码不能以任何方式更改。
请看下面的sql执行计划。这是一个使用索引筛选销售记录的应用程序查询:
sql> select *
2 from table(dbms_xplan.display_cursor(sql_id=>'f23qunrkxdgdt'));
plan_table_output
-----------------
sql_id f23qunrkxdgdt, child number 2
-------------------------------------
select sum(num) from sales where id < :idv
plan hash value: 2327341677
-------------------------------------------------------
| id | operation | name |
-------------------------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
| 2 | table access by index rowid batched| sales |
|* 3 | index range scan | salesi |
-------------------------------------------------------
如果这个计划不是最优的呢?在这个案例中,它是最佳的,但是为了示例起见,我将假定我希望oracle优化器选择一个完整的表扫描。我们需要的是full的提示:
plan_table_output
-----------------
sql_id 82x4tj3z2vg23, child number 0
-------------------------------------
select /* full(sales) */ sum(num) from sales where id < :idv
plan hash value: 1047182207
------------------------------------
| id | operation | name |
------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
|* 2 | table access full| sales |
------------------------------------
加了提示的测试查询为我们提供了一个我们期望使用的,全表扫描的执行计划的示例。现在我们可以使用spm将我们的首选计划与应用程序查询相关联。 步骤如下:
步骤0确认我们有一个sql语句,其中包含一个要更改的计划。其余步骤包括:
-
为应用程序查询创建初始禁用的sql计划基线。我之所以使用术语“一个或多个”,是因为一个查询可能有多个sql执行计划。我们将为sql语句使用的每个计划创建一个sql计划基线,但实际上只需要一个。
-
执行(或解析)有提示的测试查询以生成首选计划。
-
将在步骤2中创建的首选计划加载到sql计划基线中(这次启用了enabled=‘yes’)。
有提示的语句的文本显然和应用的语句的文本是不同的,但这正好:我们可以简单地使用执行计划而不是sql文本。只要执行计划可以重新产生并且是有效,我们的应用查询就会使用。我这么说是什么意思?请看下面的示例:
假设一个对customers的查询发生了全表扫描:
select sum(num) from customers;
如果我们使用这个查询的计划,尝试去影响我们对sales的查询,这是不能正常工作的。这就像下面这样,来要求sql plan baseline去影响对sales的执行计划:
select /* full(customers) */ sum(num) from sales where id < :idv
表象背后,是sql plan baselines使用一套完整的提示来控制执行计划。因此,我们对sales的查询,full(customers)不是一个有效的提示,并且不会产生期望的结果!如果您有时间,可以尝试将对customers查询的计划加载到与对sales查询关联的sql计划基线中。不会有错误消息,但您也无法产生您想要的计划(除非只是运气使然)。
工作示例
我已经上传了一个示例过程()和一个完整的示例到github(),这样您就可以看到上面的步骤是如何实现的。根据下面的评论,我也添加了这个过程。它以禁用状态加载所有现有的执行计划,并添加一个新的启用的执行计划到sql plan baseline(而不是替换现有的)中。你应该调整程序以适合你的具体要求。例如,您可能不希望删除预先存在的sql计划基线。
我将使用上面显示的sqlid和plan hash值。演示如何使用我的示例过程set_my_plan和add_my_plan(请参阅github中的proc.sql和proc2.sql):
set my plan procedure
执行procedure
注:“spb"表示sql plan baseline
sql> set serveroutput on
sql> set linesize 200
sql> exec set_my_plan('f23qunrkxdgdt','82x4tj3z2vg23',1047182207)
no existing sql plan baselines to drop
created 1 disabled spbs for sqlid f23qunrkxdgdt
spb detail: sql_plan_3yr9p97b3j5gbfaa7aab3 handle sql_3f5d3549d63895eb
associating plan sqlid/phv 82x4tj3z2vg23/1047182207 with spb sql handle sql_3f5d3549d63895eb
enabled spb - name: sql_plan_3yr9p97b3j5gb35032dee sql handle: sql_3f5d3549d63895eb
sql> set serveroutput off
下面是procedure执行后,对应用查询所做的explain plan的输出。未加hint的sql语句现在使用了全表扫描,并且,你可以从note部分,看到sql plan baseline已被使用的注释.
sql> select *
2 from table(dbms_xplan.display_cursor(format=>'last'));
plan_table_output
-----------------
sql_id f23qunrkxdgdt, child number 0
-------------------------------------
select sum(num) from sales where id < :idv
plan hash value: 1047182207
------------------------------------
| id | operation | name |
------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
|* 2 | table access full| sales |
------------------------------------
predicate information (identified by operation id):
---------------------------------------------------
2 - filter("id"<:idv)
note
-----
- sql plan baseline sql_plan_3yr9p97b3j5gb35032dee used for this statement
注意事项
spm使用签名来匹配sql语句,而不是sql_id。签名产生自规范化后的sql文本。因此,如果有多个sql id具有相同的签名,那么它们将共享相同的sql计划基线。例如,以下查询具有相同的签名:
select sum(num) from sales where id < :idv
select sum(num) from sales where id < :idv
select sum(num) from sales where id < :idv
上面的演示procedure会删除与应用sql语句拥有相同签名的,已存在的sql plan baseline.而新的脚本()在发现有存在的sql plan baseline时会报错,除非你使用了force参数。
一如既往,欢迎评论和更正。只需在底部发表评论
译者注: 若希望更进一步了解spm,请点此链接https://www.modb.pro/db/29991
原文链接:
原文内容:
using sql plan management to control sql execution plans
nigel bayliss
product manager
sql plan management (spm) is designed to prevent performance regression for all sql statements used by an application (assuming that the sql statements in question are used more than once). spm uses sql plan baselines that are associated with individual sql statements to control what execution plans they are permitted to use. it’s a simple but powerful idea that opens the door to the possibility of using sql plan baselines in a more selective and reactive way: to influence the sql execution plans of individual queries without having to modify application queries or change the application itself. the technique is covered in blogs and in the spm white paper here, but it deserves a post of its own along with a full example.
if you want to avoid reading all the background below and jump to a real example, i’ve added some new scripts to github. they are similar to sql profile examples published by oracle support some years ago and they demonstrate how you can retrieve plans from sql tuning sets and awr. they will work in oracle database 12c release 2 onwards (and even on oracle database 18c standard edition). the other code example below will work in oracle database 11g onwards.
consider the scenario where you have a sql statement used by an application that’s got a sub-optimal plan and you need to do something about it. for the sake of argument, let’s assume that you know that there’s a hint you can use to achieve a better plan. i’m going to assume from now on that you want to apply a hint but the application code cannot be changed in any way.
take a look at the following sql execution plan. it’s an application query that filters sales rows using an index:
sql> select *
2 from table(dbms_xplan.display_cursor(sql_id=>'f23qunrkxdgdt'));
plan_table_output
-----------------
sql_id f23qunrkxdgdt, child number 2
-------------------------------------
select sum(num) from sales where id < :idv
plan hash value: 2327341677
-------------------------------------------------------
| id | operation | name |
-------------------------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
| 2 | table access by index rowid batched| sales |
|* 3 | index range scan | salesi |
-------------------------------------------------------
what if this plan isn’t optimal? it is optimal in this case, but for the sake of example i’m going to assume that i want the oracle optimizer to pick a full table scan instead. all we need is the full hint:
plan_table_output
-----------------
sql_id 82x4tj3z2vg23, child number 0
-------------------------------------
select /* full(sales) */ sum(num) from sales where id < :idv
plan hash value: 1047182207
------------------------------------
| id | operation | name |
------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
|* 2 | table access full| sales |
------------------------------------
the hinted test query gives us an example of the table access full plan we want to use. at this point we are in a position to use spm to associate our preferred plan with the application query. here are the steps:
controlling plans with sql plan management
step zero acknowledges that we have a sql statement with a plan we want to change. the remaining steps are:
create an initial disabled sql plan baselines for the application query. i’m using the term “one or more” because a query might have more than one sql execution plan. we will create a sql plan baseline for each plan used by the sql statement, but only one is actually needed.
execute (or parse) a hinted test query to generate the preferred plan.
load the preferred plan into a sql plan baseline created in step two (this time with enabled=’yes’).
the hinted statement’s text is of course different to the application statement’s text, but that’s just fine: we’re simply using the plan and not the sql text. our application query will use the plan as long as it can reproduce it and it’s valid. what do i mean by that? here’s an example:
imagine a customers query that happens to perform a full scan:
select sum(num) from customers;
if we use the plan for this query in an attempt to influence our sales query, it’s not going to work. we would be asking the sql plan baseline to influence the sales plan like this:
select /* full(customers) */ sum(num) from sales where id < :idv
under the covers, sql plan baselines use a complete set of hints to control execution plans. so, for our sales query, full(customers) is not a valid hint and is not going to yield the desired result! if you’ve got some time on your hands, you can try loading a plan for a customers query into a sql plan baseline associated with a sales query. there won’t be an error message, but you won’t be able to reproduce the plan you want either (unless it’s just by luck).
worked example
i’ve have uploaded an example procedure and a fully worked example to github so you can see how the steps above can be implemented. based on a comment below, i added this procedure too. it loads all existing plans in a disabled state and adds a new enabled sql plan baseline (rather than replacing an existing one). you should adapt the procedures to meet your specific requirements. for example, you might not want to drop pre-existing sql plan baselines.
i’ll be using the sql ids and plan hash value that i highlighted in bold, above. here’s how to use my example procedures set_my_plan and add_my_plan (see proc.sql and proc2.sql in github):
set my plan procedure
executing the procedures
note that “spb” stands for sql plan baseline:
sql> set serveroutput on
sql> set linesize 200
sql> exec set_my_plan('f23qunrkxdgdt','82x4tj3z2vg23',1047182207)
no existing sql plan baselines to drop
created 1 disabled spbs for sqlid f23qunrkxdgdt
spb detail: sql_plan_3yr9p97b3j5gbfaa7aab3 handle sql_3f5d3549d63895eb
associating plan sqlid/phv 82x4tj3z2vg23/1047182207 with spb sql handle sql_3f5d3549d63895eb
enabled spb - name: sql_plan_3yr9p97b3j5gb35032dee sql handle: sql_3f5d3549d63895eb
sql> set serveroutput off
here’s the explain plan for the application query after the procedure was executed. the non-hinted sql statement now uses the full scan and you can see from the note section that the sql plan baseline is being used.
sql> select *
2 from table(dbms_xplan.display_cursor(format=>'last'));
plan_table_output
-----------------
sql_id f23qunrkxdgdt, child number 0
-------------------------------------
select sum(num) from sales where id < :idv
plan hash value: 1047182207
------------------------------------
| id | operation | name |
------------------------------------
| 0 | select statement | |
| 1 | sort aggregate | |
|* 2 | table access full| sales |
------------------------------------
predicate information (identified by operation id):
---------------------------------------------------
2 - filter("id"<:idv)
note
-----
- sql plan baseline sql_plan_3yr9p97b3j5gb35032dee used for this statement
usage notes
spm matches a sql statement using a signature, not a sql id. the signature is generated from the normalized sql text. for this reason, if there are multiple sql ids that have the same signature then they will all share the same sql plan baseline. for example, the following queries have the same signature:
select sum(num) from sales where id < :idv
select sum(num) from sales where id < :idv
select sum(num) from sales where id < :idv
the example procedures (above) will drop any pre-existing sql plan baselines for sql statements that have the same signature as the application sql statement. the newer scripts will generate an error if there are existing sql plan baselines unless you use the force parameter.
as always, comments and corrections are welcome. just post a comment at the bottom.