固化执行计划
操作场景
在生产环境中,同一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处的表。
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'; | 字段 | 类型 | 说明 |
|---|---|---|
| Id | bigint | 自增主键,规则的唯一标识,del_outline 使用此值删除规则。 |
| Schema_name | varchar(64) | 规则所属库名。
|
| Digest | varchar(64) | SQL语句的digest哈希(64 位十六进制串,即MySQL标准statement digest)。 |
| Digest_text | longtext | digest归一化文本(参数被替换为 ? 的语句模板),便于人工辨识。 |
| Type | enum | 规则类型,取值如下:
|
| Scope | enum |
|
| State | enum | 规则是否启用。
|
| Position | bigint |
|
| Hint | text |
|
管理Statement Outline
所有管理过程位于dbms_outln包下,具体如表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 ...;
对EXPLAIN或EXPLAIN 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');