在系统上线前,可以直接在 SQL 语句中添加 HINT,控制优化器按 HINT 指定的行为进行计划生成。

但对于已上线的业务,如果出现优化器选择的计划不够优化时,则需要在线进行计划绑定,即无需业务进行 SQL 更改,而是通过 DDL 操作将一组 HINT 加入到 SQL 中,从而使优化器根据指定的一组 HINT,对该 SQL 生成更优计划。我们将该组 HINT 称为 OUTLINE,通过对某条 SQL 创建 OUTLINT 可实现计划绑定。

OUTLINE 视图-gv$outline

字段名称类型描述
tenant_idbigint(20)租户 id
database_idbigint(20)数据库 id
outline_idbigint(20)outline id
database_namevarchar(128)数据库名称
outline_namevarchar(128)outline 名称
visible_signaturevarchar(32768)signature的反序列化结果,为了便于查看signature的信息
sql_textvarchar(32768)创建 outline 时,在 on clause 中指定的 sql
outline_targetvarchar(32768)创建 outline 时,在 to clause 中指定的 sql
outline_sqlvarchar(32768)具有完整 outline 信息的 sql

创建 OUTLINE

OceanBase 支持通过两种方式创建 OUTLINE:

  • 通过 SQL\_TEXT (用户执行的带参数的原始语句)
  • 通过SQL\_ID 创建。

注意:

创建 OUTLINE 需要进入对应数据库下执行。

使用 SQL_TEXT 创建 OUTLINE

使用 SQL_TEXT 创建 OUTLINE 后,会生成一个 key-value 对存储在 map 中,其中 key 为绑定的 SQL 参数化后的问题(具体参数化原则可参考快速参数化约束条件部分),value 为绑定的 HINT。

使用 SQL_TEXT 创建 OUTLINE 的语法如下:

  1. CREATE [OR REPLACE] OUTLINE outline_name ON stmt [ TO target_stmt ]

说明如下:

  • 指定 OR REPLACE 后,可以对已经存在执行计划进行替换。
  • 其中 stmt 一般为一个带有 HINT 和原始参数的 DML 语句。
  • 如果不指定 TO target_stmt, 则表示如果数据库接受的 SQL 参数化后与 stmt 去掉 HINT 参数化文本相同,则将该SQL 绑定 stmt 中 HINT 生成执行计划。
  • 如果期望对含有 HINT 的语句进行固定计划,则需要 TO target_stmt 来指明原始的 SQL。

注意:

在使用 target_stmt 时,严格要求 stmt 与 target_stmt 在去掉 HINT 后完全匹配。

示例如下:

  1. CREATE TABLE t1 (c1 int primary key, c2 int, c3 int, index idx_c2(c2));
  2. INSERT INTO t1 values(1, 1, 1), (2, 2, 2), (3, 3, 3);
  3. SELECT * FROM t1 WHERE c2 = 1\G;
  4. Query Plan:
  5. ===================================
  6. |ID|OPERATOR |NAME|EST. ROWS|COST|
  7. -----------------------------------
  8. |0 |TABLE SCAN|t1 |1 |37 |
  9. ===================================

优化器选择了走主键扫描,而如果数据量很增大后,执行索引 idx_c2, 可以进一步进行 SQL 优化。
此时可以通过创建 OUTLINE 将该 SQL 绑定执行索引计划。

根据如下 SQL 语句,创建 OUTLINE:

  1. CREATE OUTLINE otl_idx_c2
  2. ON SELECT/*+ index(t1 idx_c2)*/ * FROM t1 WHERE c2 = 1;

使用 SQL_ID 创建 OUTLINE

使用 SQL_ID 创建 OUTLINE 的语法如下:
CREATE OUTLINE outline_name ON sql_id USING HINT hint_text;

说明如下:

  • sql_id 为需要绑定的 SQL 对应的 SQL_ID。SQL_ID 可通过以下方式获取:

  • 查询 gv$plan_cache_plan_stat 表获取

  • 查询 gv$sql_audit 表获取
  • 通过参数化的原始 SQL,使用 MD5 生成 SQL_ID 。可参考如下脚本生成对应 SQL 的 SQL_ID。
  1. import hashlib
  2. sql_text='SELECT * FROM t1 WHERE c2 = ?'
  3. sql_id=hashlib.md5(sql_text.encode('utf-8')).hexdigest().upper()
  4. print(sql_id)

使用 SQL_ID 绑定 OUTLINE,如下例所示:

  1. CREATE OUTLINE otl_idx_c2
  2. ON "ED570339F2C856BA96008A29EDF04C74"
  3. USING HINT /*+ index(t1 idx_c2)*/ ;
  • HINT 格式为 /+ xxx /,关于 HINT 说明的详细信息,请参考优化器HINT
  • 使用 SQL_TEXT 方式创建的 OUTLINE 会覆盖 sql_id 方式创建的 OUTLINE。SQL_TEXT 方式创建的优先级高;
  • 如果 sql_id 对应的 SQL 语句已经有 HINT,则创建 OUTLINE 指定的 HINT 会覆盖原始语句中所有 HINT。

OUTLINE DATA 是优化器为了完全复现某一计划而生成的一组 HINT 信息,以 BEGIN_OUTLINE_DATA 开始,并以END_OUTLINE_DATA 结束。

OUTLINE DATA 可以通过 explain extended 命令获得,如下例所示:

  1. OceanBase(admin@test)> explain extended SELECT/*+ index(t1 idx_c2)*/ * FROM t1 WHERE c2 = 1;
  2. explain extended SELECT/*+ index(t1 idx_c2)*/ * FROM t1 WHERE c2 = 1;
  3. | =========================================
  4. |ID|OPERATOR |NAME |EST. ROWS|COST|
  5. -----------------------------------------
  6. |0 |TABLE SCAN|t1(idx_c2)|1 |88 |
  7. =========================================
  8. Outputs & filters:
  9. -------------------------------------
  10. 0 - output([t1.c1(0x7ff95ab37448)], [t1.c2(0x7ff95ab33090)], [t1.c3(0x7ff95ab377f0)]), filter(nil),
  11. access([t1.c2(0x7ff95ab33090)], [t1.c1(0x7ff95ab37448)], [t1.c3(0x7ff95ab377f0)]), partitions(p0),
  12. is_index_back=true,
  13. range_key([t1.c2(0x7ff95ab33090)], [t1.c1(0x7ff95ab37448)]), range(1,MIN ; 1,MAX),
  14. range_cond([t1.c2(0x7ff95ab33090) = 1(0x7ff95ab309f0)])
  15. Used Hint:
  16. -------------------------------------
  17. /*+
  18. INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2")
  19. */
  20. Outline Data:
  21. -------------------------------------
  22. /*+
  23. BEGIN_OUTLINE_DATA
  24. INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2")
  25. END_OUTLINE_DATA
  26. */
  27. Plan Type:
  28. -------------------------------------
  29. LOCAL
  30. Optimization Info:
  31. -------------------------------------
  32. t1:table_rows:3, physical_range_rows:1, logical_range_rows:1, index_back_rows:1, output_rows:1, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[idx_c2], pruned_index_name[t1]
  33. level 0:
  34. ***********
  35. paths(@1101710651081553(ordering([t1.c2], [t1.c1]), cost=87.951827))

其中 OUTLINE DATA 如下例所示:

  1. /*+
  2. BEGIN_OUTLINE_DATA
  3. INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2")
  4. END_OUTLINE_DATA
  5. */

OUTLINE DATA 也是 HINT,因此可以用在计划绑定的过程中,如下例所示:

  1. CREATE OUTLINE otl_idx_c2
  2. ON "ED570339F2C856BA96008A29EDF04C74"
  3. USING HINT /*+
  4. BEGIN_OUTLINE_DATA
  5. INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2")
  6. END_OUTLINE_DATA
  7. */;

删除 OUTLINE

OUTLINE 的语法如下:

  1. DROP OUTLINE outline_name;

说明如下:

  • 删除 OUTLINE 后,对应 SQL 重新生成计划时将不再依据绑定的 outline 生成.

注意:

删除 OUTLINE 需要 outline_name 中指定数据库名称,或者使用数据库后执行。

确定 OUTLINE 创建生效

确定创建的 OUTLINE 是否成功且符合预期,需要进行如下三步的验证:

  1. 确定是否创建 OUTLINE 成功。

通过查看 gv$outline 中的表,确认是否成功创建对应的 outline 名称的 OUTLINE。

  1. select * from oceanbase.gv$outline where outline_name = 'otl_idx_c2'\G;
  2. *************************** 1. row ***************************
  3. tenant_id: 1001
  4. database_id: 1100611139404776
  5. outline_id: 1100611139404777
  6. database_name: test
  7. outline_name: otl_idx_c2
  8. visible_signature: SELECT * FROM t1 WHERE c2 = ?
  9. sql_text: SELECT/*+ index(t1 idx_c2)*/ * FROM t1 WHERE c2 = 1
  10. outline_target:
  11. outline_sql: SELECT /*+ BEGIN_OUTLINE_DATA INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2") END_OUTLINE_DATA*/* FROM t1 WHERE c2 = 1
  1. 确定新的 SQL 执行是否通过绑定的 outline 生成了新计划。

当绑定 OUTLINE 的 SQL 有新的流量查询后,查询 gv$plan_cache_plan_stat 表中该 SQL 对应的计划信息中 outline_id,如果 outline_id 是在 gv$outline 中查到的 outline_id 则表示该计划是按绑定的 outline 生成的执行计划,否则不是。

  1. select sql_id, plan_id, statement, outline_id, outline_data
  2. from oceanbase.gv$plan_cache_plan_stat
  3. where statement like '%SELECT * FROM t1 WHERE c2 =%'\G
  4. *************************** 1. row ***************************
  5. sql_id: ED570339F2C856BA96008A29EDF04C74
  6. plan_id: 17225
  7. statement: SELECT * FROM t1 WHERE c2 = ?
  8. outline_id: 1100611139404777
  9. outline_data: /*+ BEGIN_OUTLINE_DATA INDEX(@"SEL$1" "test.t1"@"SEL$1" "idx_c2") END_OUTLINE_DATA*/
  1. 确定生成的执行计划是否符合预期。
    确定是通过绑定的 outline 生成的计划后,需要确定生成的计划是否符合预期,可以通过查询gv$plan_cache_plan_stat 表查看 plan_cache 中缓存的执行计划形状, 具体查看方式可参考实时执行计划展示介绍。
  1. select OPERATOR, NAME
  2. from oceanbase.gv$plan_cache_plan_explain
  3. where tenant_id = 1001 and ip = '10.101.163.87'
  4. and port = 30474 and plan_id = 17225;
  5. +--------------------+------------+
  6. | OPERATOR | NAME |
  7. +--------------------+------------+
  8. | PHY_ROOT_TRANSMIT | NULL |
  9. | PHY_TABLE_SCAN | t1(idx_c2) |
  10. +--------------------+------------+

计划绑定与执行计划缓存关系

  • 使用 SQL_TEXT 创建 OUTLINE 后, SQL 请求生成新计划查找 OUTLINE 使用的 key 与计划缓存使用的 key 相同,均是 SQL 参数化后的文本串;
  • 当创建和删除 OUTLINE 后,对应 SQL 有新的请求时,会触发执行计划缓存中对应执行计划失效,更新为按绑定的 OUTLINE 生成的执行计划。