更新时间:2026-08-05 GMT+08:00
分享

固化执行计划

操作场景

在生产环境中,同一SQL语句的执行计划可能因数据分布变化、统计信息更新、优化器版本升级等原因而发生改变,从而导致性能抖动。固化执行计划(Statement Outline)通过将Optimizer Hint和Index Hint与SQL模板(digest)绑定,在不修改应用SQL文本的前提下,对匹配的语句自动注入指定的Hint,从而稳定执行计划。

约束限制

  • 仅RDS for MySQL 8.0.32及更高版本支持Statement Outline功能。
  • 如需开启Statement Outline,请提交工单申请。开启后会对所有经过解析的语句生效,存在额外的digest计算与匹配开销,规则数量较多时需评估对性能的影响。
  • 规则的添加(add_*_outline)、删除(del_outline)、刷新(flush_outline)需要root账号执行,show_outline与preview_outline不需要任何权限。
  • Statement Outline是节点级特性,规则仅在当前节点生效,不会在节点间自动同步。哪个节点需要规则生效,就在对应节点上设置。

功能介绍

Statement Outline支持两类Hint,分别为Optimizer Hints和Index Hints,具体如下:

  • Optimizer Hints(优化器Hint)

    通过add_optimizer_outline添加。Hint文本以 /*+ ... */ 形式存储,在优化期间作用于指定的查询块(Position表示查询块编号,1表示顶层查询块)。支持MySQL 8.0社区优化器Hint体系。

    表1 Optimizer Hints作用范围

    类别

    示例Hint

    说明

    语句执行时间

    /*+ MAX_EXECUTION_TIME(1000) */

    仅对顶层独立SELECT生效。

    变量设置

    /*+ SET_VAR(optimizer_switch='mrr_cost_based=off') */

    对单条语句设置会话级变量;不能设置全局变量(如 collation_server)。

    JOIN 顺序

    /*+ JOIN_ORDER(o, c) */、/*+ JOIN_PREFIX(o) */、/*+ JOIN_FIXED_ORDER() */

    控制表连接顺序。

    表级

    /*+ BNL(c, o) */

    控制连接算法(块嵌套循环等)。

    索引级

    /*+ INDEX(o idx_cust) */、/*+ NO_INDEX(o idx_cust) */、/*+ INDEX_MERGE(o idx_cust, idx_status) */

    控制索引选择。

    子查询

    /*+ SEMIJOIN(@subq1 MATERIALIZATION, DUPSWEEDOUT) */

    控制半连接策略。

    命名查询块

    /*+ QB_NAME(subq1) */

    为查询块命名,配合其它Hint引用。

    优化器Hint的写法与MySQL 8.0社区一致。Position指明Hint作用于第几个查询块,对UNION各分支可用不同Position分别指定。

  • Index Hints(索引Hint)

    通过add_index_outline添加,规则类型为 USE INDEX / IGNORE INDEX / FORCE INDEX,按MySQL索引Hint语义作用于指定Position处的表。

    • 类型(Type):USE INDEX(建议使用)、IGNORE INDEX(忽略)、FORCE INDEX(强制使用)。

      其中,USE INDEX的Hint列允许为空,表示不使用任何索引(等价USE INDEX());FORCE INDEX与IGNORE INDEX的Hint列不允许为空。

    • 作用范围(Scope):''(全部)、FOR JOIN、FOR ORDER BY、FOR GROUP BY。
    • 多索引:Hint列支持以逗号分隔的多个索引名。示例:idx_cust, idx_status。

Statement Outline表介绍

规则持久化在系统表mysql.outline。SHOW CREATE TABLE mysql.outline 输出如下:

CREATE TABLE `outline` (
  `Id` bigint NOT NULL AUTO_INCREMENT,
  `Schema_name` varchar(64) COLLATE utf8mb3_bin DEFAULT NULL,
  `Digest` varchar(64) COLLATE utf8mb3_bin NOT NULL,
  `Digest_text` longtext COLLATE utf8mb3_bin,
  `Type` enum('IGNORE INDEX','USE INDEX','FORCE INDEX','OPTIMIZER')
      CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
  `Scope` enum('','FOR JOIN','FOR ORDER BY','FOR GROUP BY')
      CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT '',
  `State` enum('N','Y')
      CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'Y',
  `Position` bigint NOT NULL,
  `Hint` text COLLATE utf8mb3_bin NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_bin
  STATS_PERSISTENT=0 ROW_FORMAT=DYNAMIC COMMENT='Statement outline';
表2 字段说明

字段

类型

说明

Id

bigint

自增主键,规则的唯一标识,del_outline 使用此值删除规则。

Schema_name

varchar(64)

规则所属库名。

  • 为空字符串 `''` 表示全局规则,匹配任意当前库。
  • 非空时仅当会话当前库等于该值才匹配。允许为NULL。

Digest

varchar(64)

SQL语句的digest哈希(64 位十六进制串,即MySQL标准statement digest)。

Digest_text

longtext

digest归一化文本(参数被替换为 ? 的语句模板),便于人工辨识。

Type

enum

规则类型,取值如下:

  • IGNORE INDEX
  • USE INDEX
  • FORCE INDEX
  • OPTIMIZER

Scope

enum

  • 索引Hint的作用范围:''、FOR JOIN、FOR ORDER BY、FOR GROUP BY。
  • 优化器Hint固定为 ''。

State

enum

规则是否启用。

  • Y:表示启用(默认)。
  • N:表示停用。

Position

bigint

  • 索引Hint为目标表在语句表列表中的序号(从1开始)。
  • 优化器Hint为目标查询块编号(1为顶层)。

Hint

text

  • 索引Hint为索引名列表(逗号分隔)。
  • 优化器Hint为 /*+ ... */ 文本。

管理Statement Outline

所有管理过程位于dbms_outln包下,具体如表3

表3 管理过程

过程

参数签名

权限

add_optimizer_outline

(VARCHAR schema, VARCHAR digest, LONGLONG position, VARCHAR hint, VARCHAR sql)

root

add_index_outline

(VARCHAR schema, VARCHAR digest, LONGLONG position, VARCHAR type, VARCHAR hint, VARCHAR scope, VARCHAR sql)

root

preview_outline

(VARCHAR schema, VARCHAR query)

无需权限

show_outline

()

无需权限

del_outline

(LONGLONG id)

root

flush_outline

()

root

功能验证

可通过以下两种方式验证outline是否生效。

preview_outline不会真正执行语句,仅返回给定语句命中的outline规则。

CALL dbms_outln.add_index_outline('outline_demo', '', 1, 'USE INDEX', 'idx_cust', '',
    "select * from orders where orders.cust_id = 1001 and orders.status = 'paid'");
 
CALL dbms_outln.preview_outline('outline_demo',
    "select * from orders where orders.cust_id = 1001 and orders.status = 'paid'");

返回结果:

+--------------+------------------------------------------------------------------+------------+------------+-------+------------------------+
| SCHEMA       | DIGEST                                                           | BLOCK_TYPE | BLOCK_NAME | BLOCK | HINT                   |
+--------------+------------------------------------------------------------------+------------+------------+-------+------------------------+
| outline_demo | 6f2547f7b071f58bba4510761664f1031690cf572e644596fe709f81223bf321 | TABLE      | orders     |     1 | USE INDEX (`idx_cust`) |
+--------------+------------------------------------------------------------------+------------+------------+-------+------------------------+
1 row in set (0.00 sec)

结果中BLOCK_TYPE为TABLE(索引Hint)或QUERY(优化器Hint),HINT列为将被注入的Hint文本。若返回预期规则,说明匹配正确。

为语句添加outline后,用EXPLAIN查看实际计划是否被改写。

USE outline_demo;
TRUNCATE TABLE mysql.outline;
CALL dbms_outln.flush_outline();
CALL dbms_outln.add_index_outline('outline_demo', '', 1, 'USE INDEX', 'idx_cust', '',
    "select * from orders where orders.cust_id = 1001 and orders.status = 'paid'");
 
EXPLAIN SELECT * FROM orders WHERE orders.cust_id = 1002 AND orders.status = 'paid';

返回结果:

+----+-------------+--------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
| id | select_type | table  | partitions | type | possible_keys | key      | key_len | ref   | rows | filtered | Extra       |
+----+-------------+--------+------------+------+---------------+----------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | orders | NULL       | ref  | idx_cust      | idx_cust | 5       | const |    1 |   100.00 | Using where |
+----+-------------+--------+------------+------+---------------+----------+---------+-------+------+----------+-------------+

key列显示使用了idx_cust,而非优化器自行选择的索引。再通过SHOW WARNINGS查看改写后的语句:

SHOW WARNINGS;

返回结果:

+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                                       |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `outline_demo`.`orders`.`order_id` AS `order_id`,`outline_demo`.`orders`.`cust_id` AS `cust_id`,`outline_demo`.`orders`.`amount` AS `amount`,`outline_demo`.`orders`.`status` AS `status` from `outline_demo`.`orders` USE INDEX (`idx_cust`) where ((`outline_demo`.`orders`.`status` = 'paid') and (`outline_demo`.`orders`.`cust_id` = 1002)) |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

改写后的语句中可见USE INDEX (...),说明outline已注入并生效。

EXPLAIN支持多种格式,outline在以下格式下均生效:

EXPLAIN FORMAT = TRADITIONAL SELECT ...;
EXPLAIN FORMAT = JSON        SELECT ...;
EXPLAIN FORMAT = TREE         SELECT ...;
EXPLAIN ANALYZE              SELECT ...;
EXPLAIN ANALYZE FORMAT = TREE SELECT ...;

EXPLAINEXPLAIN ANALYZE [FORMAT=...]语句做匹配时,内核会剥离EXPLAIN前缀,按被解释语句本身的digest查找,因此无需为EXPLAIN单独建规则。

案例说明

下面给出常见的使用案例,使用前先准备示例库与表,并确保已开启Statement Outline,再通过CALL dbms_outln.*语句添加规则。

CREATE DATABASE outline_demo;
CREATE TABLE outline_demo.orders(order_id int auto_increment primary key,
                                 cust_id int,
                                 amount decimal(10,2),
                                 status varchar(20),
                                 key idx_cust(cust_id),
                                 key idx_status(status)) engine = innodb;
INSERT INTO outline_demo.orders VALUES(1, 1001, 199.00, 'paid');

相关文档