实例运行过程中,一些 SQL 的执行计划可能会发生非预期的变化,造成稳定性风险。
例如:
在线业务使用 order by limit n
的用法,在 n 值比较小的时候频繁出现执行计划变动影响性能,
数据量变化时,统计数据也会发生改变。这些变化可能引起优化器的代价选择出现错误判断,选择了非最优的索引。
临时解决办法如实例通过调整 optimizer_switch
参数,或者通过修改业务 SQL 添加 hint 来绑定执行计划,对业务都不够友好。而 Statement outline 功能可将 SQL 的执行计划与 SQL 查询模板绑定,用户无需修改 SQL 即可将查询计划固化,避免出现上述风险。
缩略语 | 英文全名 | 中文解释 |
---|---|---|
Outline | Statement outline | 执行计划固化,用于解决生产环境中 SQL 语句的执行计划经常会发生改变,而导致数据库不稳定的问题。 |
Package | Native package procedure framework | 原生工具包,是一组预定义的存储过程(stored procedure)的逻辑集合,方便使用一个领域的相关存储过程。 |
Digest | SQL Digest | SQL 指纹,在 MySQL 8.0 中默认使用 SHA256 值,每条模板有唯一的值。 |
Digest Text | SQL Digest Text | SQL 模板,将 SQL 参数去除后的 SQL 语句。 |
参数名 | 级别 | 取值范围 | 默认值 | 描述 |
---|---|---|---|---|
loose_outline_enabled | GLOBAL | [ON | OFF] | ON |
说明
该参数为非开放的可修改参数,如需修改请提交工单联系技术支持。
已创建 MySQL 8.0 实例,且实例的内核小版本为 20250215 或更新版本。关于创建实例和查看实例内核小版本的相关信息,请参见创建实例。
说明
如您的实例是在 2025 年 02 月 15 日之后创建,则自动使用 20250215 或更新的内核小版本。如您的实例是在 2025 年 02 月 15 日之前创建,则会使用 20250215 之前的内核小版本。此时,您可通过手动方式升级实例的内核小版本,详细信息,请参见手动升级实例内核小版本。
MySQL 通过 hint 提供了语句维度的优化器控制,通过将 hint 添加至 SQL 语句,即可对 SQL 查询器的决策进行干预:
SELECT /*+ NO_RANGE_OPTIMIZATION(t3 PRIMARY, f2_idx) */ f1 FROM t3 WHERE f1 > 30 AND f1 < 33; SELECT /*+ BKA(t1) NO_BKA(t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ NO_ICP(t1, t2) */ * FROM t1 INNER JOIN t2 WHERE ...; SELECT /*+ SEMIJOIN(FIRSTMATCH, LOOSESCAN) */ * FROM t1 ...; EXPLAIN SELECT /*+ NO_ICP(t1) */ * FROM t1 WHERE ...; SELECT /*+ MERGE(dt) */ * FROM (SELECT * FROM t1) AS dt; INSERT /*+ SET_VAR(foreign_key_checks=OFF) */ INTO t2 VALUES(2);
Statement outline 功能支持把两类 Hint 绑定至 SQL 模板中,从而达到查询计划固化的效果。支持的 Hint 类型与 MySQL 8.0 一致:
云数据库 MySQL 版的 8.0 内置了 Outline 相关的原生存储过程(Native Package)工具包,用户可通过调用 dbms_outline
相关接口进行 Outline 的操作。
描述:增加 Optimizer Hints。
语法:
CALL dbms_outline.insert_optimizer_outline('<Schema_name>','<Digest>','<Query_block>','<Hint>','<Query>');
参数说明:
参数 | 说明 |
---|---|
Schema_name | 数据库名称。 说明 通过添加空<Schema_name>: |
Digest | 查询对应的 SQL 指纹。 |
Query_Block | 需要应用该 Hint 的关键字(如SELECT ,UPDATE 等)编号,从1开始 |
Hint | Hint 表示完整的 Hint 字符串,例如 /*+ NO_ICP(t1, t2) */ 。 |
Query | 需要添加 Statement outline 的原始 SQL 语句。 |
说明
- Digest 和 Query(原始 SQL语句)可以任选其一。如果填写 Query,`dbms_outline` 会计算指纹(Digest)和 SQL 模板(Digest\_text)。 - Query 语句中需要使用引号时,需要在 query 语句中给需要添加引号的部分添加单引号,并使用双引号包围 query。
示例:
CALL dbms_outline.insert_optimizer_outline('outline_db', '', 1, '/*+ MAX_EXECUTION_TIME(1000) */', 'SELECT * FROM t1 WHERE id = 1');
描述:增加 Index Hints。
语法:
CALL dbms_outline.insert_index_outline('<Schema_name>','<Digest>','<Position>','<Type>','<Hint>','<Scope>','<Query>');
参数说明
参数 | 说明 |
---|---|
Schema_name | 数据库名称。 说明 通过添加空<Schema_name>: |
Digest | 查询对应的SQL 指纹。 |
Position | 需要应用该 Hint 的表的位置,从1开始 |
Type | Index Hint 类型。取值为 USE INDEX 、FORCE INDEX 或 IGNORE INDEX 。 |
Hint | 索引名字的列表, 例如 idx_1,idx_2。 |
Scope | 分为如下三类:
|
Query | 需要添加 Statement outline 的原始 SQL 语句。 |
说明
dbms_outline
会计算指纹(Digest)和 SQL 模板(Digest_text)。示例:
-- 使用原始 SQL 语句 CALL dbms_outline.insert_index_outline('outline_db', '', 1, 'FORCE INDEX', 'idx_1', '',"SELECT * FROM t1 WHERE t1.col1 = 1 AND t1.col2 = 'mytest'"); -- 使用 SQL 指纹 CALL dbms_outline.insert_index_outline('outline_db', 'b4369611be7ab2d27c85897632576a04bc08f50b928a1d735b62d0a140628c4c', 1, 'USE INDEX', 'idx_1', '', "");
描述:验证 SQL 语句 Statement outline 的生效情况,如果找到了匹配值,该 SQL 模板将会应用指定的 Hint。
语法:
CALL dbms_outline.check_outline('<Schema_name>', '<Query>');
参数说明:
参数 | 说明 |
---|---|
Schema_name | 数据库名称。 说明 通过添加空 <Schema_name>: |
Query | 需要添加 Statement outline 的原始 SQL 语句。 |
示例:
CALL dbms_outline.check_outline('outline_db', "SELECT * FROM t1 WHERE t1.col1 =1 AND t1.col2 ='mytest'"); +------------+------------------------------------------------------------------+------------+------------+-------+---------------------+ | SCHEMA | DIGEST | BLOCK_TYPE | BLOCK_NAME | BLOCK | HINT | +------------+------------------------------------------------------------------+------------+------------+-------+---------------------+ | outline_db | b4369611be7ab2d27c85897632576a04bc08f50b928a1d735b62d0a140628c4c | TABLE | t1 | 1 | USE INDEX (`idx_1`) | +------------+------------------------------------------------------------------+------------+------------+-------+---------------------+
Explain:
通过 EXPLAIN
语句也可以查看 Outline 是否生效。
explain select * from t1 where t1.col1 =1 and t1.col2 ='mytest'; +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ref | idx_1 | idx_1 | 5 | const | 1 | 100.00 | Using where | +----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.00 sec) mysql> show warnings; +-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` USE INDEX (`idx_1`) where ((`outline_db`.`t1`.`col1` = 1) and (`outline_db`.`t1`.`col2` = 'mytest')) | +-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
描述:查看系统中保存 Statement outline 以及生效情况。
语法:
CALL dbms_outline.display_outline();
示例:
CALL dbms_outline.display_outline(); +------+------------+------------------------------------------------------------------+-----------+-------+------+--------------------+------+----------+-----------------------------------------------------------------------------------------------------------------+ | ID | SCHEMA | DIGEST | TYPE | SCOPE | POS | HINT | HIT | OVERFLOW | DIGEST_TEXT | +------+------------+------------------------------------------------------------------+-----------+-------+------+--------------------+------+----------+-----------------------------------------------------------------------------------------------------------------+ | 1 | outline_db | ab234071d1d3af13a775f45b532639203e1aa8c3ec63b289a75b9fb862c61c4c | USE INDEX | | 1 | idx_1 | 0 | 0 | SELECT * FROM `t1` WHERE `t1` . `id` = ? UNION SELECT * FROM `t1` WHERE `t1` . `col1` = ? AND `t1` . `col2` = ? | | 2 | outline_db | ab234071d1d3af13a775f45b532639203e1aa8c3ec63b289a75b9fb862c61c4c | OPTIMIZER | | 2 | /*+ QB_NAME(q2) */ | 0 | 0 | SELECT * FROM `t1` WHERE `t1` . `id` = ? UNION SELECT * FROM `t1` WHERE `t1` . `col1` = ? AND `t1` . `col2` = ? | +------+------------+------------------------------------------------------------------+-----------+-------+------+--------------------+------+----------+-----------------------------------------------------------------------------------------------------------------+
说明
HIT
表示该 Statement outline 命中的次数,OVERFLOW
表示该 Statement outline 只匹配了 schema 和 digest 而没有找到 Query Block 或相应的表的次数。描述:删除内存和表中的某一条 Statement outline。
语法:
CALL dbms_outline.delete_outline(<Id>);
参数说明:
参数 | 说明 |
---|---|
Id | Outline ID。 |
示例:
CALL dbms_outline.delete_outline(2);
如果删除的 Outline 不存在,会返回警告,使用 SHOW WARNINGS
查看警告内容。
--删除不存在的 outline CALL dbms_outline.delete_outline(999999); Query OK, 0 rows affected, 2 warnings (0.00 sec) SHOW WARNINGS; +---------+------+------------------------------------------------+ | Level | Code | Message | +---------+------+------------------------------------------------+ | Warning | 7521 | Statement outline 999999 is not found in table | | Warning | 7521 | Statement outline 999999 is not found in cache | +---------+------+------------------------------------------------+ 2 rows in set (0.00 sec)
CALL dbms_outline.insert_index_outline('outline_db', '', 1, 'USE INDEX', 'idx_1', '', "select * from t1 where t1.col1 =1 and t1.col2 ='mytest'");
示例
CALL dbms_outline.insert_optimizer_outline('outline_db', '', 1, '/*+ BNL(t1,t2) */', "select t1.id, t2.id from t1,t2"); mysql> explain select t1.id, t2.id from t1,t2; +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------------------+ | 1 | SIMPLE | t1 | NULL | index | NULL | ind_1 | 5 | NULL | 1 | 100.00 | Using index | | 1 | SIMPLE | t2 | NULL | index | NULL | ind_1 | 5 | NULL | 1 | 100.00 | Using index; Using join buffer (Block Nested Loop) | +----+-------------+-------+------------+-------+---------------+-------+---------+------+------+----------+----------------------------------------------------+ 2 rows in set, 1 warning (0.01 sec) mysql> show warnings; +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ BNL(`t1`@`select#1`) BNL(`t2`@`select#1`) */ `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t2`.`id` AS `id` from `outline_db`.`t1` join `outline_db`.`t2` | +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
示例说明:
BNL 表示 Block Nested Loop,这是一种表连接算法。其核心逻辑是:
将驱动表(如 t1
)的数据按块(Block)加载到内存中
在内存块中逐行与被驱动表(如 t2
)的数据进行匹配
适用于驱动表较小且被驱动表无高效索引的场景。
作用对象 BNL(t1, t2)
表示强制优化器在表 t1
和 t2
的关联操作中采用块嵌套循环连接算法。
设置子查询的 Query block 标识
CALL dbms_outline.insert_optimizer_outline('outline_db', '', 2, ' /*+ QB_NAME(subq1) */', "SELECT * FROM t1 WHERE t1.col1 IN (SELECT col1 FROM t2)");
使用 SEMIJOIN 相关 HINT
CALL dbms_outline.insert_optimizer_outline('outline_db', '', 1, '/*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */ ', "SELECT * FROM t1 WHERE t1.col1 IN (SELECT col1 FROM t2)");
示例说明:
SEMIJOIN
表示该提示针对半连接操作,即通过子查询(如 IN
或 EXISTS
)筛选主表数据时,仅匹配子查询中至少一条相关记录的场景
@subq1
标识需要优化的子查询名称或别名,此处为上一个 Outline 指定的子查询名称
MATERIALIZATION
物化策略:强制将子查询的结果临时存储在内存或磁盘中,避免重复计算。适用于子查询结果集较小但需要多次访问的场景
DUPSWEEDOUT
去重策略:在子查询结果中消除重复值,确保主查询仅匹配唯一值。常用于子查询可能返回重复数据时优化性能
效果验证
mysql> explain SELECT * FROM t1 WHERE t1.col1 IN (SELECT col1 FROM t2); +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ALL | ind_1 | NULL | NULL | NULL | 1 | 100.00 | Using where | | 1 | SIMPLE | <subquery2> | NULL | eq_ref | <auto_distinct_key> | <auto_distinct_key> | 5 | outline_db.t1.col1 | 1 | 100.00 | NULL | | 2 | MATERIALIZED | t2 | NULL | index | ind_1 | ind_1 | 5 | NULL | 1 | 100.00 | Using index | +----+--------------+-------------+------------+--------+---------------------+---------------------+---------+--------------------+------+----------+-------------+ 3 rows in set, 1 warning (0.00 sec) mysql> show warnings; +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ SEMIJOIN(@`subq1` MATERIALIZATION, DUPSWEEDOUT) */ `outline_db`.`t1`.`id` AS `id`,`outline_db`.`t1`.`col1` AS `col1`,`outline_db`.`t1`.`col2` AS `col2` from `outline_db`.`t1` semi join (`outline_db`.`t2`) where (`<subquery2>`.`col1` = `outline_db`.`t1`.`col1`) | +-------+------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
通过以下hint可以单独对语句设置变量
/*+ SET_VAR(<var_name>=<var_value>) */
Outline 示例:
设置最大查询时间 MAX_EXECUTION_TIME
CALL dbms_outline.insert_optimizer_outline("outline_db", '', 1, '/*+ MAX_EXECUTION_TIME(1000) */', "select * from t1 where id = 1");
设置外键约束 foreign_key_checks
CALL dbms_outline.insert_optimizer_outline("outline_db", '', 1, '/*+ SET_VAR(foreign_key_checks=OFF) */', "select * from t1 where id = 1");
效果验证
mysql> explain select * from t1 where id = 1; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | no matching row in const table | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+--------------------------------+ 1 row in set, 1 warning (0.01 sec) mysql> show warnings; +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Level | Code | Message | +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Note | 1003 | /* select#1 */ select /*+ MAX_EXECUTION_TIME(1000) SET_VAR(foreign_key_checks='OFF') */ NULL AS `id`,NULL AS `col1`,NULL AS `col2` from `outline_db`.`t1` where multiple equal(1, NULL) | +-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
如果用户插入了两条矛盾的 Outline,实际起作用的 hint 由 MySQL 的 hint resolve 逻辑决定。详情请参考MySQL文档。
示例
mysql> CALL dbms_outline.display_outline(); +------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | ID | SCHEMA | DIGEST | TYPE | SCOPE | POS | HINT | HIT | OVERFLOW | DIGEST_TEXT | +------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | 14 | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | OPTIMIZER | | 2 | /*+ QB_NAME(subq2) */ | 6 | 0 | SELECT * FROM `db1` . `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `db1` . `t2` ) | | 15 | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | OPTIMIZER | | 2 | /*+ NO_INDEX(t1) */ | 2 | 0 | SELECT * FROM `db1` . `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `db1` . `t2` ) | | 16 | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | OPTIMIZER | | 2 | /*+ INDEX(t1) */ | 2 | 0 | SELECT * FROM `db1` . `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `db1` . `t2` ) | | 17 | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | OPTIMIZER | | 2 | /*+ INDEX(t2) */ | 0 | 0 | SELECT * FROM `db1` . `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `db1` . `t2` ) | | 18 | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | OPTIMIZER | | 2 | /*+ NO_INDEX(t2) */ | 0 | 0 | SELECT * FROM `db1` . `t1` WHERE `t1` . `col1` IN ( SELECT `col1` FROM `db1` . `t2` ) | +------+------------+------------------------------------------------------------------+-----------+-------+------+-------------------------------------------------------+------+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ mysql> CALL dbms_outline.check_outline('', 'SELECT * FROM db1.t1 WHERE t1.col1 IN (SELECT col1 FROM db1.t2)'); +--------+------------------------------------------------------------------+------------+------------+-------+----------------------------------------------------+ | SCHEMA | DIGEST | BLOCK_TYPE | BLOCK_NAME | BLOCK | HINT | +--------+------------------------------------------------------------------+------------+------------+-------+----------------------------------------------------+ | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | QUERY | | 1 | /*+ NO_INDEX(`t1`@`subq2`) INDEX(`t2`@`subq2`) */ | | | d9bc7ca4b466e3eff5c35133b62328b340e7996a20c0a3278f44e81908516796 | QUERY | | 2 | /*+ QB_NAME(`subq2`) */ | +--------+------------------------------------------------------------------+------------+------------+-------+----------------------------------------------------+ 2 rows in set, 2 warnings (0.00 sec) mysql> show warnings; +---------+------+-----------------------------------------------------------+ | Level | Code | Message | +---------+------+-----------------------------------------------------------+ | Warning | 3126 | Hint INDEX(`t1` ) is ignored as conflicting/duplicated | | Warning | 3126 | Hint NO_INDEX(`t2` ) is ignored as conflicting/duplicated | +---------+------+-----------------------------------------------------------+ 2 rows in set (0.00 sec)
示例说明
Query block 2 中只有 t2,但 Outline 指定了对于 t1 的 index,而且对 t2 的 index 同时指定了 NO_INDEX 和 INDEX。这时候虽然所有 hint 都能匹配,但是最后生效的是第一个插入的 Outline:NO_INDEX(t1)
与INDEX(t2)
。
如果出现矛盾的 Outline,会返回警告,使用SHOW WARNINGS
查看警告内容。
Outline 相关管理 SQL 接口只能在主节点执行,调用下列 SQL 接口之后,Outline 的修改自动会同步至所有只读节点和备节点并且生效:
insert_optimizer_outline
insert_index_outline
delete_outline
Outline 验证以及显示接口可在任意节点执行:
display_outline
check_outline
开启 Statement outline 后,SQL 执行时会计算一次 SQL 指纹。
对性能几乎无影响,在受 SQL 指纹计算影响最大的 CPU-bound 性能测试中,Outline 对性能的影响 < 3%。
如果您选择了「恢复到新实例」,会从新实例全量恢复实例数据到指定时间点,系统中保存的 Outline 也表将会回到指定时间点的状态。
如果您选择了「恢复到原实例」,您将指定用户库表进行恢复,此时 Outline 将保持原样。