功能介绍
statement outline 是腾讯云 TXSQL 内核提供的在数据库服务器端用于干预 SQL 执行计划的特性。针对低效执行计划引发的某些 SQL 响应时间大幅增加以及因消耗更多的资源而导致整体负载的性能衰退问题,该特性支持在不修改业务 SQL 代码、无需发版的前提下,控制产生更好的执行计划,以快速恢复业务。
支持版本
内核版本 TDSQL-C MySQL 版3.1.16.003及以上。
工作原理
statement outline 的工作原理是基于 MySQL 的两个基本能力来设计:
SQL 解析时会自动产生相应的语句摘要
语句摘要称为 Statement Digest Text 和 Statement Digest,分别是原始文本和哈希值。需要注意的是语句摘要信息是不包含具体值的。
SQL 的优化过程可以通过优化指令来控制
这些优化指令称为 Optimizer Hints。
基于以上的基本能力,就可以在数据库服务器端保存一些规则来匹配语句摘要和注入优化指令。
一条 SQL 可以有多个候选执行计划,如果每个执行计划用一个点来表示,那么这些被注入的优化指令相当于画了一个圈,圈里面只有最快的那个点。这个圈就称为 Outline(轮廓)。这个圈也可以画大一点,里面可能圈住多个点,但排除了那些慢的点。
statement outline 的核心逻辑就是找到性能差的语句,圈出最快的执行计划。
功能限制
说明:
语句摘要文本的最大长度是
max_digest_length,这是一个只读变量,默认值是1024。若语句摘要超出了最大长度,则会发生截断,这时 digest 匹配就会失效。修改这个参数需要重启数据库服务器。只有 Top-Level Statement 才会计算语句摘要。因此,存储过程里的语句当前是不能干预的。
语句摘要哈希值的运算是加密运算,会有一些 CPU 开销。通常是可以忽略的,但如果这些开销不可忽略,则只能关闭功能,在业务 SQL 里嵌入优化指令,或者彻底优化 SQL 的写法。
摘要文本的计算逻辑并不能产生严格意义上的参数化语句。虽然对于简单语句二者形式上是相同的,但实践上会有二者不同的情况。例如,对于 col IN (1,2),参数化语句里是 col IN (?,?),而摘要文本里会是 col IN (...)。
Optimizer Hints 会有一些快捷路径影响代价计算,例如,Last_query_cost 可能会产生明显偏低的代价。
Optimizer Hints 作为优化指令,对于优化器而言是建议而不是强制措施,优化器有时候是可以忽略某些优化指令的。例如,若某个操作在优化过程中不发生,那么相应的优化指令就是无效的。可以通过观察性能干预前后的表现来检验,或者也可以通过 EXPLAIN 后 SHOW WARNINGS 里的 expanded query 进行检验核对。
MySQL Optimizer Hints 存在一些设计缺陷,例如,不能在一个查询块里区分名字相同的不同对象,即使它们有不同的来源。
statement outline 对于一个参数化语句只能定义一组优化指令,需要灵活地运用优化指令来圈定合适的执行计划。
使用教程
使用 statement outline 分为三步:
3. 运行目标语句观察生效情况
以下为您详细讲解。
假设在 test 库里已经准备好了 t1 表。执行如下命令:
CREATE TABLE t1 (id INT AUTO_INCREMENT PRIMARY KEY,col1 INT,col2 VARCHAR(100),KEY ind_1(col1),KEY ind_2(col2));INSERT INTO t1 VALUES(1, 2, 'txsql');ANALYZE TABLE t1;
执行如下查询计划:
说明:
假设对于测试语句
SELECT * FROM t1 WHERE col1 = 2 AND col2 = 'mysql' 默认执行计划。即采用索引 ind_1 的计划,不是一个很快的计划,更好的是采用索引 ind_2 的计划。txsql> explain SELECT * FROM t1 WHERE col1 = 2 AND col2 = 'mysql';+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| 1 | SIMPLE | t1 | NULL | ref | ind_1,ind_2 | ind_1 | 5 | const | 1 | 100.00 | Using where |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
显然,这个执行计划是可以通过 INDEX(t1 ind_2) 这个 hint 来控制的。
txsql> explain SELECT /*+ INDEX(t1 ind_2) */ * FROM t1 WHERE col1 = 2 AND col2 = 'mysql';+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| 1 | SIMPLE | t1 | NULL | ref | ind_2 | ind_2 | 403 | const | 1 | 100.00 | Using where |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
那么,就可以用 statement outline 来告诉 TXSQL 优化器采用索引 ind_2 的执行计划,而不必修改业务 SQL 来嵌入这些优化指令。
确认已启用 statement outline 功能模块
statement outline 功能模块通过 statement_outline_enabled 参数控制。默认是启用的。您可通过如下命令查询是否启用:
txsql> SHOW GLOBAL VARIABLES LIKE 'statement_outline_enabled';+---------------------------+-------+| Variable_name | Value |+---------------------------+-------+| statement_outline_enabled | ON |+---------------------------+-------+1 row in set (0.02 sec)
如果没有启用,则可以通过如下命令来启用:
SET GLOBAL statement_outline_enabled = ON;
通过 statement outline 规则接口写入规则
用
dbms_admin.statement_outline_add_rule() 存储过程写入规则,在参数中传入能产生预期执行计划的具体语句。存储过程的参数是默认库名和具体 SQL 。库名是用来支持解析 SQL 的,所以,这两者必须是匹配的。txsql> CALL dbms_admin.statement_outline_add_rule('test', "SELECT /*+ INDEX(t1 ind_2) */ * FROM t1 WHERE col1 = 2 AND col2 = 'mysql'");+------+| ID |+------+| 4 |+------+1 row in set (0.00 sec)
写入的规则会同时存在内部缓存和系统表 mysql.statement_outline_rules 里。
执行如下命令查看 statement outline 内部缓存:
txsql> CALL dbms_admin.statement_outline_show_rules()\\G*************************** 1. row ***************************ID: 4SCHEMA: testDIGEST: c3fa7558f4dd3dd0713428fbd60beda118f35ed9c447ce58b2bb9239539fab76ENABLED: YESHITS: 0OUTLINE: [{"hint": "/*+ INDEX(`t1`@`select#1` `ind_2`) */ ", "select_number": 1}]DIGEST_TEXT: select /*+ INDEX(`t1`@`select#1` `ind_2`) */ * from `test`.`t1` where ((`col1` = ?) and (`col2` = ?))HAS_APPLY_FAILURE: 01 row in set (0.01 sec)
执行如下命令查看存储 statement outline 规则的系统表:
txsql> select * from mysql.statement_outline_rules\\G*************************** 1. row ***************************id: 4schema_name: testdigest: c3fa7558f4dd3dd0713428fbd60beda118f35ed9c447ce58b2bb9239539fab76outline: [{"hint": "/*+ INDEX(`t1`@`select#1` `ind_2`) */ ", "select_number": 1}]enabled: YESdigest_text: select /*+ INDEX(`t1`@`select#1` `ind_2`) */ * from `test`.`t1` where ((`col1` = ?) and (`col2` = ?))1 row in set (0.00 sec)
可以看到,内部缓存和系统表 mysql.statement_outline_rules 里有相同的规则定义,但缓存里还有两个计数器,分别表示规则匹配的次数 (HITS) 和规则错误的次数 (HAS_APPLY_FAILURE)。
Statement Digest 和 Optimizer Hints 都是 MySQL 的基础能力。所以,这里 Digest 和原始文本也是可以用 MySQL 原生函数计算的:
txsql> select STATEMENT_DIGEST("select * from t1 where col1 = 2 AND col2 = 'mysql'");+------------------------------------------------------------------------+| STATEMENT_DIGEST("select * from t1 where col1 = 2 AND col2 = 'mysql'") |+------------------------------------------------------------------------+| c3fa7558f4dd3dd0713428fbd60beda118f35ed9c447ce58b2bb9239539fab76 |+------------------------------------------------------------------------+1 row in set (0.00 sec)txsql> select STATEMENT_DIGEST_TEXT("select * from t1 where col1 = 2 AND col2 = 'mysql'");+-----------------------------------------------------------------------------+| STATEMENT_DIGEST_TEXT("select * from t1 where col1 = 2 AND col2 = 'mysql'") |+-----------------------------------------------------------------------------+| SELECT * FROM `t1` WHERE `col1` = ? AND `col2` = ? |+-----------------------------------------------------------------------------+1 row in set (0.00 sec)
注意:
statement outline 规则里的 DIGEST_TEXT 和 DIGEST 不是对应的,而分别是规则参数语句的 DIGEST TEXT 和业务语句的 DIGEST 。
运行目标语句观察生效情况
statement outline 规则生效体现在两个方面:
运行时语句产生符合预期的执行计划。
内部缓存的规则计数变化。
重新执行测试语句,就可以看到即使没有内嵌的优化指令,执行计划也切换到 ind_2 上。此外,从
SHOW WARNINGS 返回的 expanded query 里,也可以看到采用了 statement outline 规则里的 hints。在实际业务场景中,执行受控 SQL 时就能感受到性能变化。txsql> explain select * from t1 where col1 = 2 and col2 = 'mysql';+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| 1 | SIMPLE | t1 | NULL | ref | ind_2 | ind_2 | 403 | const | 1 | 100.00 | Using where |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)txsql> show warnings;+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Level | Code | Message |+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Note | 1003 | /* select#1 */ select /*+ INDEX(`t1`@`select#1` `ind_2`) */ `test`.`t1`.`id` AS `id`,`test`.`t1`.`col1` AS `col1`,`test`.`t1`.`col2` AS `col2` from `test`.`t1` where ((`test`.`t1`.`col2` = 'mysql') and (`test`.`t1`.`col1` = 2)) |+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+1 row in set (0.01 sec)
这是一次成功的匹配和采用,所以,匹配计数增加,失败计数不变。
txsql> CALL dbms_admin.statement_outline_show_rules()\\G*************************** 1. row ***************************ID: 4SCHEMA: testDIGEST: c3fa7558f4dd3dd0713428fbd60beda118f35ed9c447ce58b2bb9239539fab76ENABLED: YESHITS: 1OUTLINE: [{"hint": "/*+ INDEX(`t1`@`select#1` `ind_2`) */ ", "select_number": 1}]DIGEST_TEXT: select /*+ INDEX(`t1`@`select#1` `ind_2`) */ * from `test`.`t1` where ((`col1` = ?) and (`col2` = ?))HAS_APPLY_FAILURE: 01 row in set (0.00 sec)
参考手册
statement outline 在数据库服务器端内部缓存和系统表里保存规则。功能启用后,会自动对运行的语句进行干预。
功能参数
参数名 | 动态 | 类型 | 默认 | 参数值范围 | 说明 |
statement_outline_enabled | yes | Boolean | ON | OFF | ON | 控制 statement outline 功能的开启和关闭 ON:开启 OFF:关闭 |
statement_outline_enable_apply | yes | Boolean | ON | OFF | ON | 控制会话级 statement outline 功能的开启和关闭 ON:开启 OFF:关闭 |
statement_outline_partitions | no | Integer | 16 | [1, 256] | 内部缓存分区数 |
statement_outline_apply_verbose | yes | Boolean | OFF | OFF | ON | 此参数开启时会有更丰富的报错信息。 ON:开启 OFF:关闭 |
状态计数
参数名 | 类型 | 说明 |
Statement_outline_digest_truncated | Integer | 语句摘要发生截断时,新建规则或者匹配已有规则摘要的计数。此时 vebose 模式下会提示 Digest truncated,应该调大 max_digest_length |
命令语法
CALL dbms_admin.statement_outline_add_rule(default_db, hinted_query) :增加一条规则,返回 rule_id。语句摘要和优化指令都从 hinted_query 中提取,但是规则里的语句摘要是不含 hints 的。CALL dbms_admin.statement_outline_delete_rule(rule_id) :删除 rule_id 对应的规则。CALL dbms_admin.statement_outline_show_rules() :列出内部缓存里的所有规则,包含匹配计数和错误计数。CALL dbms_admin.statement_outline_enable_rule(rule_id, enabled) :启用或者禁止 rule_id 对应的规则。enabled 取值为1,表示启用,取值为0,表示禁止。CALL dbms_admin.statement_outline_flush_rules() 清空内部缓存,并从系统表 mysql.statement_outline_rules 重新加载规则。缓存里的计数都会归零。直接导入系统表然后刷新缓存,可以实现批量插入规则。错误反馈
在执行目标语句时,outline hints 可能会产生关于错误的告警。
以下三个错误反馈分别对应增删改命令,表示参数或者规则状态有问题,具体提示请看相应的 message。
Could not add statement outline rule: {message}
Could not delete statement outline rule: {message}
Could not enable or disable statement outline rule: {message}
以下两个错误表示重新加载命令的状态。如果发生这些错误,相应规则会无效。通过比较内部缓存和系统表里的规则集,可以知道哪些规则没有加载成功。
Error when refreshing statement outline rules: {message}
Invalid statement outline rule {rule_id} in rules table: {message}
以下错误表示规则应用失败。发生规则应用失败时,会反映到规则错误计数上。
The statement fail to apply rule {rule_id}: {reason}
规则没有加载成功,或者规则应用失败,一般都是非预期错误。可能是因为规则系统或者 Optimizer Hints 基础能力缺陷。如果遇到这类问题,请 提交工单 反馈。注意,如果是采用直接导入系统表的方式插入规则,请务必自行检查规则定义是合法的。
Statement outline is disabled for current query: {reason}
有些情况下,即使有 outline 规则与该语句摘要匹配,也要禁止应用该规则。例如,当语句摘要计算发生截断时,此时 reason 为 Digest truncated。
常见问题
CALL dbms_admin.txsql_api(args) 是什么用法?
CALL dbms_admin.txsql_api(args) 是调用 TXSQL 原生的存储过程,其中 dbms_admin 是预留的库名。在预留库名里面注册自定义函数或存储过程,保持调用风格的统一。这样做只是为了隔离管理命令对 SQL 解析器的依赖,毕竟 SQL 标准并没有定义这些庞大的管理接口体系。
statement outline 可以干预某个具体语句,而不是参数化语句吗?
目前 statement outline 是基于语句摘要的,而摘要是参数化文本,因此,它不能直接干预具体语句。
如果业务 SQL 里已经使用了 FORCE INDEX (idx),那么,还能用 statement outline 来控制执行计划吗?
从社区版本8.0.22开始,MySQL 补齐了 Optimizer Hints 对索引的控制,不再提倡使用 FORCE INDEX 这些 index hints。Index hints 存在一个潜在问题,即如果发生了索引变更,这些语句就会报语法错误,但 INDEX() 等优化指令只会报 warning,不会打断语句执行。
在设计上, Optimizer Hints 优先于 index hints,例如:
txsql> explain select /*+ INDEX(t1 ind_2) */ * from t1 FORCE INDEX (ind_1) where col1 = 2 and col2 = 'mysql';+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+| 1 | SIMPLE | t1 | NULL | ref | ind_2 | ind_2 | 403 | const | 1 | 100.00 | Using where |+----+-------------+-------+------------+------+---------------+-------+---------+-------+------+----------+-------------+1 row in set, 1 warning (0.00 sec)
需要注意的是 force index 也是 digest 的构成元素,因此,在配置规则时,要在 hinted query 里也带上这些内容,否则就会导致规则匹配不上。
但是,如果业务 SQL 里本身就带 Optimizer Hint,那就没法用 CALL dbms_admin.statement_outline_add_rule(default_db, hinted_query) 了,因为它在构造规则时会自动排除 hints。这时候可以用修改系统表后 flush 的办法。
CALL dbms_admin.statement_outline_show_rules() 打印的结果和系统表 mysql.statement_outline_rules 里的内容怎么是不同的?
dbms_admin 接口会同时更新内部缓存和系统表,但是,系统表是可以直接修改的,所以可能发生二者不同步的情况,这时可以用 CALL dbms_admin.statement_outline_flush_rules() 来刷新内部缓存。此外,内部缓存中有规则和计数,而系统表中只有规则定义,这在设计上就是不同的。
可以给一个模板配置多条规则吗?
如果配置了多条规则,则 statement outline 目前只会选第一条匹配的规则。您可以通过 CALL dbms_admin.statement_outline_show_rules() 查看具体是哪条规则 HITS 计数增加。因为 outline 规则里是多个 hint,并且这个集合是可以编辑的,具体做法是修改系统表然后 flush。因此,用多条规则来表示 hints 集合,就没有意义了。