计划绑定¶
计划绑定是通过对SQL语句创建Outline来实现的,Outline本质是一组Hint,在执行SQL语句时,匹配对应的Outline,将Hint写入到SQL中来控制优化器输出绑定的计划
为什么引入计划绑定¶
在数据库运行过程中,由于统计信息的更新、表结构的变更、索引的添加或删除等多种因素,优化器可能会为相同的SQL查询生成不同的执行计划。这种变化有时会导致查询性能显著下降,特别是在复杂查询和频繁执行的查询中更为明显。
当发现了性能问题,可以经过分析后在SQL上添加Hint来使优化器输出我们想要的计划,但是对于已经上线的业务,想要修改原SQL并不容易,故需要一种在不修改原SQL的基础上也能改变执行计划的手段,为此引入了计划绑定功能。
Outline视图¶
DBA用户可以通过V_DBA_OUTLINE视图查询,普通用户可以通过V_USER_OUTLINE视图查询,各字段及含义如下。
| 列名 | 类型 | 描述 |
|---|---|---|
| OUTLINE_ID | OID | 对象标识符 |
| OUTLINE_NAME | NAME | 对象名字 |
| OUTLINE_OWNER | OID | 对象所有者 |
| OUTLINE_ON_SQL | TEXT | 绑定SQL的计划来源 |
| OUTLINE_MATCH_SQL | TEXT | 绑定的SQL |
| OUTLINE_DATA | TEXT | 用来固定计划的Hint组 |
| OUTLINE_ENABLE | BOOLEAN | 是否启用 |
使用Outline进行计划绑定¶
创建Outline语法见 Create Outline
Outline_Data可以通过 Explain Outline 命令获取
-- 启用outline
set ENABLE_USE_OUTLINE = TRUE;
-- 创建环境
CREATE TABLE T1(A INT);
INSERT INTO T1 VALUES(1);
ANALYZE T1;
-- 查看计划
EXPLAIN ANALYZE SELECT * FROM T1,T1 TT WHERE T1.A = TT.A;
QUERY PLAN
----------------------------------------------------------------------------------------------------
Nested Loop(Inner Join) (cost=0.00..4.01 rows=1 width=8) (actual time=0.06..0.10 rows=4 loops=1)
Join Filter: ("outer".A = "inner".A)
-> Seq Scan on T1 (cost=0.00..2.00 rows=1 width=4) (actual time=0.02..0.03 rows=2 loops=1)
-> Seq Scan on T1 TT (cost=0.00..2.00 rows=1 width=4) (actual time=0.01..0.02 rows=2 loops=2)
Planning Time: 0.49 msec
Execution Time: 0.23 msec
(6 rows)
-- 此时可以看到因为表行数比较少所以选择了 nestloop 连接
-- 接着向表中插入大量数据后不更新统计信息进行查询
SELECT COUNT(*) FROM T1;
COUNT
-------
1024
(1 row)
-- 可以看到因为统计信息未更新,导致优化器选择了一个性能较差得计划
EXPLAIN ANALYZE SELECT * FROM T1,T1 TT WHERE T1.A = TT.A;
QUERY PLAN
------------------------------------------------------------------------------------------------------------
Nested Loop(Inner Join) (cost=0.00..14.03 rows=1 width=8) (actual time=0.06..870.51 rows=1048576 loops=1)
Join Filter: ("outer".A = "inner".A)
-> Seq Scan on T1 (cost=0.00..7.01 rows=1 width=4) (actual time=0.03..0.74 rows=1024 loops=1)
-> Seq Scan on T1 TT (cost=0.00..7.01 rows=1 width=4) (actual time=0.01..0.55 rows=1024 loops=1024)
Planning Time: 0.20 msec
Execution Time: 964.08 msec
(6 rows)
-- 通过增加Hint找到了一个更优的计划
EXPLAIN ANALYZE SELECT /*+ USE_HJ(T1,TT) */ * FROM T1,T1 TT WHERE T1.A = TT.A;
QUERY PLAN
------------------------------------------------------------------------------------------------------------
Hash Join(Inner Join) (cost=7.02..14.05 rows=1 width=8) (actual time=1.94..255.37 rows=1048576 loops=1)
Hash Cond: ("outer".A = "inner".A)
-> Seq Scan on T1 (cost=0.00..7.01 rows=1 width=4) (actual time=0.02..1.16 rows=1024 loops=1)
-> Hash (cost=7.01..7.01 rows=1 width=4) (actual time=1.89..1.89 rows=0 loops=1)
Buckets: 1033 Batches: 1 Memory Usage: 28kB
-> Seq Scan on T1 TT (cost=0.00..7.01 rows=1 width=4) (actual time=0.02..0.89 rows=1024 loops=1)
Planning Time: 0.25 msec
Execution Time: 351.65 msec
(8 rows)
-- 通过创建 Outline 将更快的计划绑定到当前SQL上
CREATE OUTLINE OUT1 ON SELECT /*+ USE_HJ(T1,TT) */ * FROM T1,T1 TT WHERE T1.A = TT.A MATCH SELECT * FROM T1,T1 TT WHERE T1.A = TT.A;
-- 查看系统视图可以看到绑定的计划
select OUTLINE_NAME,OUTLINE_MATCH_SQL,OUTLINE_DATA from V_DBA_OUTLINE;
OUTLINE_NAME | OUTLINE_MATCH_SQL | OUTLINE_DATA
--------------+-------------------------------------------+----------------------------------------------------------------------------------------------------------------------
OUT1 | SELECT * FROM T1,T1 TT WHERE T1.A = TT.A | /*+ FULL(@SEL$1 SYSDBA.T1@SEL$1) FULL(@SEL$1 SYSDBA.TT@SEL$1) USE_HJ(@SEL$1 (SYSDBA.T1@SEL$1),(SYSDBA.TT@SEL$1)) */
(1 row)
Outline_Data是优化器为了绑定指定得计划生成的一组Hint,Hint详情参见 HINT使用参考
Outline_Data可以通过 Explain Outline 命令获取
EXPLAIN OUTLINE SELECT * FROM T1,T1 TT WHERE T1.A = TT.A;
QUERY PLAN
------------------------------------------------------------------
Hash Join(Inner Join) (cost=7.02..14.05 rows=1 width=8)
Hash Cond: ("outer".A = "inner".A)
-> Seq Scan on T1 (cost=0.00..7.01 rows=1 width=4)
-> Hash (cost=7.01..7.01 rows=1 width=4)
-> Seq Scan on T1 TT (cost=0.00..7.01 rows=1 width=4)
Outline
-------
FULL(@SEL$1 SYSDBA.T1@SEL$1)
FULL(@SEL$1 SYSDBA.TT@SEL$1)
USE_HJ(@SEL$1 (SYSDBA.T1@SEL$1),(SYSDBA.TT@SEL$1))
Note: use outline OUT1
(15 rows)
确定计划绑定生效¶
-- 此时再执行已经进行计划绑定的SQL
-- 从计划上可以看出执行计划已经变成了绑定的计划,并且会提示 Note: use outline OUT1
EXPLAIN ANALYZE SELECT * FROM T1,T1 TT WHERE T1.A = TT.A;
QUERY PLAN
------------------------------------------------------------------------------------------------------------
Hash Join(Inner Join) (cost=7.02..14.05 rows=1 width=8) (actual time=1.03..257.10 rows=1048576 loops=1)
Hash Cond: ("outer".A = "inner".A)
-> Seq Scan on T1 (cost=0.00..7.01 rows=1 width=4) (actual time=0.03..1.40 rows=1024 loops=1)
-> Hash (cost=7.01..7.01 rows=1 width=4) (actual time=0.98..0.98 rows=0 loops=1)
Buckets: 1033 Batches: 1 Memory Usage: 28kB
-> Seq Scan on T1 TT (cost=0.00..7.01 rows=1 width=4) (actual time=0.01..0.50 rows=1024 loops=1)
Planning Time: 0.49 msec
Execution Time: 356.96 msec
Note: use outline OUT1
(10 rows)
修改Outline¶
修改Outline语法见 Alter Outline
修改Outline名字
ALTER OUTLINE OUT1 RENAME TO OUT2;
SELECT OUTLINE_NAME,OUTLINE_MATCH_SQL FROM V_DBA_OUTLINE;
OUTLINE_NAME | OUTLINE_MATCH_SQL
--------------+-------------------------------------------
OUT2 | SELECT * FROM T1,T1 TT WHERE T1.A = TT.A
(1 row)
重建Outline
随着数据的变化,之前固定的计划可能不是最优的,此时可以对Outline进行重建,重新固定为最优的执行计划。
CREATE TABLE T1(A INT);
INSERT INTO T1 VALUES(1);
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 SELECT * FROM T1;
INSERT INTO T1 VALUES(2);
ANALYZE T1;
SELECT COUNT(*) FROM T1;
COUNT
-------
8193
(1 row)
-- 创建Outline
CREATE OUTLINE OUT1 ON SELECT /*+ INDEX(T1) */ * FROM T1 WHERE A = 2 MATCH SELECT * FROM T1 WHERE A = 2;
-- 此时OUTLINE中固定的是顺序扫描计划
SELECT OUTLINE_NAME,OUTLINE_DATA FROM SYS_OUTLINE;
OUTLINE_NAME | OUTLINE_DATA
--------------+--------------------------------------
OUT1 | /*+ FULL(@SEL$1 SYSDBA.T1@SEL$1) */
(1 row)
-- 创建索引后,应该走索引更快,所以重建后应该走索引扫描
CREATE INDEX IDX1 ON T1(A);
-- 重建OUTLINE
ALTER OUTLINE OUT1 REBUILD;
-- 此时OUTLINE中固定的是索引扫描计划
SELECT OUTLINE_NAME,OUTLINE_DATA FROM SYS_OUTLINE;
SELECT OUTLINE_NAME,OUTLINE_DATA FROM SYS_OUTLINE;
OUTLINE_NAME | OUTLINE_DATA
--------------+--------------------------------------------
OUT1 | /*+ INDEX(@SEL$1 SYSDBA.T1@SEL$1 IDX1) */
(1 row)
开启/禁用OUTLINE
set ENABLE_USE_OUTLINE = TRUE;
CREATE TABLE T1(A INT);
CREATE INDEX IDX1 ON T1(A);
-- 创建Outline
CREATE OUTLINE OUT1 ON SELECT /*+ FULL(T1) */ * FROM T1 WHERE A = 1 MATCH SELECT /*+ INDEX(T1) */ * FROM T1 WHERE A = 1;
-- 禁用outline
ALTER OUTLINE out1 DISABLE;
EXPLAIN SELECT /*+ INDEX(T1) */ * FROM T1 WHERE A = 1;
QUERY PLAN
----------------------------------------------------------------------------------
Index Scan using IDX1(Fast Index Scan) on T1 (cost=0.00..1.41 rows=41 width=4)
Index Key: (A = 1)
(2 rows)
-- 启用outline
ALTER OUTLINE out1 ENABLE;
EXPLAIN SELECT /*+ INDEX(T1) */ * FROM T1 WHERE A = 1;
QUERY PLAN
------------------------------------------------------
Seq Scan on T1 (cost=0.00..110.40 rows=41 width=4)
Scan Key: (A = 1)
Note: use outline OUT1
(4 rows)
OUTLINE_REBUILD_ADVISOR¶
为了防止绑定的计划随着数据的变化导致性能严重下降,提供了OUTLINE_REBUILD_ADVISOR视图来建议哪些Outline需要重建。
此视图的工作原理为计算Outline在REBUILD前后的计划代价,如果REBUILD后能得到一个更好的计划,则输出这条OUTLINE,DBA可以定期查询视图后对OUTLINE进行手动REBUILD
--CUR_OUTLINE_COST为当前Outline计划代价,REBUILD_OUTLINE_COST为重建后Outline计划代价
select * from OUTLINE_REBUILD_ADVISOR;
OUTLINE_ID | OUTLINE_NAME | OUTLINE_OWNER | CUR_OUTLINE_COST | REBUILD_OUTLINE_COST
------------+--------------+---------------+------------------+----------------------
34829 | OUT1 | 1 | 154 | 1
(1 row)