MySQL8 中文参考(二十一)(4)https://developer.aliyun.com/article/1566173
7.6.4 Rewriter 查询重写插件
7.6.4.1 安装或卸载 Rewriter 查询重写插件
7.6.4.2 使用 Rewriter 查询重写插件
7.6.4.3 Rewriter 查询重写插件参考
MySQL 支持查询重写插件,可以在服务器执行之前检查并可能修改服务器接收到的 SQL 语句。请参阅 查询重写插件。
MySQL 发行版包括一个名为 Rewriter
的后解析查询重写插件以及用于安装插件及其相关元素的脚本。这些元素共同工作,提供语句重写功能:
- 一个名为
Rewriter
的服务器端插件检查语句并可能根据其内存中的重写规则缓存对其进行重写。 - 以下语句可能会被重写:
- 截至 MySQL 8.0.12:支持
SELECT
、INSERT
、REPLACE
、UPDATE
和DELETE
。 - 在 MySQL 8.0.12 之前:仅支持
SELECT
。
- 独立语句和预编译语句可能会被重写。出现在视图定义或存储程序中的语句不会被重写。
Rewriter
插件使用名为query_rewrite
的数据库,其中包含名为rewrite_rules
的表。该表为插件决定是否重写语句提供持久存储的规则。用户通过修改存储在此表中的规则集与插件进行通信。插件通过设置表行的message
列与用户进行通信。query_rewrite
数据库包含一个名为flush_rewrite_rules()
的存储过程,将规则表的内容加载到插件中。- 一个名为
load_rewrite_rules()
的可加载函数由flush_rewrite_rules()
存储过程使用。 Rewriter
插件公开了系统变量,使插件配置和状态变量提供运行时操作信息。在 MySQL 8.0.31 及更高版本中,该插件还支持一个权限(SKIP_QUERY_REWRITE
),用于保护特定用户的查询免受重写。
以下部分描述了如何安装和使用 Rewriter
插件,并提供了其相关元素的参考信息。
7.6.4.1 安装或卸载 Rewriter 查询重写插件
注意
如果安装了Rewriter
插件,即使禁用了也会带来一些开销。为避免这种开销,除非打算使用该插件,否则不要安装它。
要安装或卸载Rewriter
查询重写插件,请选择位于 MySQL 安装的share
目录中的适当脚本:
install_rewriter.sql
:选择此脚本以安装Rewriter
插件及其相关元素。uninstall_rewriter.sql
:选择此脚本以卸载Rewriter
插件及其相关元素。
按照以下方式运行所选脚本:
$> mysql -u root -p < install_rewriter.sql Enter password: *(enter root password here)*
此处示例使用install_rewriter.sql
安装脚本。如果要卸载插件,请替换为uninstall_rewriter.sql
。
运行安装脚本应该会安装并启用插件。要验证,请连接到服务器并执行以下语句:
mysql> SHOW GLOBAL VARIABLES LIKE 'rewriter_enabled'; +------------------+-------+ | Variable_name | Value | +------------------+-------+ | rewriter_enabled | ON | +------------------+-------+
有关使用说明,请参见第 7.6.4.2 节,“使用 Rewriter 查询重写插件”。有关参考信息,请参见第 7.6.4.3 节,“Rewriter 查询重写插件参考”。
7.6.4.2 使用 Rewriter 查询重写插件
要启用或禁用插件,请启用或禁用rewriter_enabled
系统变量。默认情况下,安装插件时Rewriter
插件是启用的(参见第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。要显式设置初始插件状态,可以在服务器启动时设置该变量。例如,要在选项文件中启用插件,请使用以下行:
[mysqld] rewriter_enabled=ON
也可以在运行时启用或禁用插件:
SET GLOBAL rewriter_enabled = ON; SET GLOBAL rewriter_enabled = OFF;
假设Rewriter
插件已启用,它会检查并可能修改服务器接收到的每个可重写语句。插件根据其内存中的重写规则缓存来决定是否重写语句,这些规则从query_rewrite
数据库中的rewrite_rules
表中加载。
这些语句会被重写:
- 截至 MySQL 8.0.12:
SELECT
、INSERT
、REPLACE
、UPDATE
和DELETE
。 - 在 MySQL 8.0.12 之前:仅
SELECT
。
独立语句和准备语句会被重写。在视图定义或存储程序中出现的语句不会被重写。
从 MySQL 8.0.31 开始,具有SKIP_QUERY_REWRITE
权限的用户运行的语句不会被重写,前提是rewriter_enabled_for_threads_without_privilege_checks
系统变量设置为OFF
(默认为ON
)。这可用于控制语句和应该保持不变的语句,例如来自由CHANGE REPLICATION SOURCE TO
指定的SOURCE_USER
的语句。对于由 MySQL 客户端程序执行的语句,包括mysqlbinlog、mysqladmin、mysqldump和mysqlpump;因此,您应该授予SKIP_QUERY_REWRITE
给这些实用程序用于连接到 MySQL 的用户帐户或帐户。
- 添加重写规则
- 语句匹配工作原理
- 重写准备语句
- 重写插件操作信息
- 字符集的重写插件使用
添加重写规则
要为Rewriter
插件添加规则,请向rewrite_rules
表添加行,然后调用flush_rewrite_rules()
存储过程将规则从表中加载到插件中。以下示例创建了一个简单的规则,用于匹配选择单个文字值的语句:
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement) VALUES('SELECT ?', 'SELECT ? + 1');
结果表内容如下所示:
mysql> SELECT * FROM query_rewrite.rewrite_rules\G *************************** 1\. row *************************** id: 1 pattern: SELECT ? pattern_database: NULL replacement: SELECT ? + 1 enabled: YES message: NULL pattern_digest: NULL normalized_pattern: NULL
规则指定了一个模式模板,指示要匹配哪些SELECT
语句,并指定了一个替换模板,指示如何重写匹配的语句。但是,将规则添加到rewrite_rules
表中并不足以使Rewriter
插件使用该规则。您必须调用flush_rewrite_rules()
将表内容加载到插件的内存缓存中:
mysql> CALL query_rewrite.flush_rewrite_rules();
提示
如果您的重写规则似乎无法正常工作,请确保通过调用flush_rewrite_rules()
重新加载规则表。
当插件从规则表中读取每个规则时,它会从模式计算出一个规范化(语句摘要)形式和一个摘要哈希值,并使用它们来更新normalized_pattern
和pattern_digest
列:
mysql> SELECT * FROM query_rewrite.rewrite_rules\G *************************** 1\. row *************************** id: 1 pattern: SELECT ? pattern_database: NULL replacement: SELECT ? + 1 enabled: YES message: NULL pattern_digest: d1b44b0c19af710b5a679907e284acd2ddc285201794bc69a2389d77baedddae normalized_pattern: select ?
有关语句摘要、规范化语句和摘要哈希值的信息,请参见第 29.10 节,“性能模式语句摘要和采样”。
如果由于某些错误而无法加载��则,则调用flush_rewrite_rules()
会产生一个错误:
mysql> CALL query_rewrite.flush_rewrite_rules(); ERROR 1644 (45000): Loading of some rule(s) failed.
当发生这种情况时,插件会将错误消息写入规则行的message
列,以传达问题。检查rewrite_rules
表,查看具有非NULL
message
列值的行,以查看存在哪些问题。
模式使用与准备语句相同的语法(参见第 15.5.1 节,“PREPARE 语句”)。在模式模板中,?
字符充当匹配数据值的参数标记。?
字符不应包含在引号内。参数标记仅可用于数据值应出现的位置,不能用于 SQL 关键字、标识符、函数等。插件解析语句以识别文本值(如第 11.1 节,“文本值”中定义的那样),因此您可以在任何文本值的位置放置参数标记。
像模式一样,替换内容可以包含?
字符。对于与模式模板匹配的语句,插件会重写它,使用数据值替换替换中的?
参数标记,这些数据值由模式中相应标记匹配的数据值确定。结果是一个完整的语句字符串。插件要求服务器解析它,并将重写后的语句表示返回给服务器。
添加并加载规则后,请检查是否根据语句是否与规则模式匹配而进行重写:
mysql> SELECT PI(); +----------+ | PI() | +----------+ | 3.141593 | +----------+ 1 row in set (0.01 sec) mysql> SELECT 10; +--------+ | 10 + 1 | +--------+ | 11 | +--------+ 1 row in set, 1 warning (0.00 sec)
第一个SELECT
语句不会进行重写,但第二个会。第二个语句说明了当Rewriter
插件重写语句时,会生成警告消息。要查看消息,请使用SHOW WARNINGS
:
mysql> SHOW WARNINGS\G *************************** 1\. row *************************** Level: Note Code: 1105 Message: Query 'SELECT 10' rewritten to 'SELECT 10 + 1' by a query rewrite plugin
语句不必重写为相同类型的语句。以下示例加载一个将DELETE
语句重写为UPDATE
语句的规则:
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement) VALUES('DELETE FROM db1.t1 WHERE col = ?', 'UPDATE db1.t1 SET col = NULL WHERE col = ?'); CALL query_rewrite.flush_rewrite_rules();
要启用或禁用现有规则,请修改其enabled
列并重新加载表到插件中。要禁用规则 1:
UPDATE query_rewrite.rewrite_rules SET enabled = 'NO' WHERE id = 1; CALL query_rewrite.flush_rewrite_rules();
这使您可以停用规则而无需将其从表中删除。
要重新启用规则 1:
UPDATE query_rewrite.rewrite_rules SET enabled = 'YES' WHERE id = 1; CALL query_rewrite.flush_rewrite_rules();
rewrite_rules
表包含一个pattern_database
列,Rewriter
用于匹配未使用数据库名称限定的表名:
- 语句中的限定表名仅在相应数据库和表名相同的情况下与模式中的限定名称匹配。
- 语句中的未限定表名仅在默认数据库与
pattern_database
相同且表名相同的情况下与模式中的未限定名称匹配。
假设一个名为appdb.users
的表有一个名为id
的列,并且应用程序预期使用以下形式之一的查询从表中选择行,第二种形式可以在appdb
是默认数据库时使用:
SELECT * FROM users WHERE appdb.id = *id_value*; SELECT * FROM users WHERE id = *id_value*;
还假设id
列被重命名为user_id
(也许必须修改表以添加另一种 ID,并且有必要更明确地指示id
列代表什么类型的 ID)。
更改意味着应用程序必须在WHERE
子句中引用user_id
而不是id
,但无法更新的旧应用程序将不再正常工作。Rewriter
插件可以通过匹配和重写有问题的语句来解决此问题。要将语句SELECT * FROM appdb.users WHERE id = *
value*
匹配并重写为SELECT * FROM appdb.users WHERE user_id = *
value*
,您可以向重写规则表中插入代表替换规则的行。如果还想使用未限定表名匹配此SELECT
,还需要添加一个显式规则。使用?
作为值占位符,需要的两个INSERT
语句如下:
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement) VALUES( 'SELECT * FROM appdb.users WHERE id = ?', 'SELECT * FROM appdb.users WHERE user_id = ?' ); INSERT INTO query_rewrite.rewrite_rules (pattern, replacement, pattern_database) VALUES( 'SELECT * FROM users WHERE id = ?', 'SELECT * FROM users WHERE user_id = ?', 'appdb' );
添加两个新规则后,执行以下语句使其生效:
CALL query_rewrite.flush_rewrite_rules();
Rewriter
使用第一条规则匹配使用限定表名的语句,第二条规则匹配使用未限定名称的语句。只有当appdb
是默认数据库时,第二条规则才有效。
语句匹配原理
Rewriter
插件使用语句摘要和摘要哈希值来在各个阶段匹配传入语句与重写规则。max_digest_length
系统变量确定用于计算语句摘要的缓冲区大小。较大的值可以计算出区分较长语句的摘要。较小的值使用更少的内存,但增加了较长语句与相同摘要值发生冲突的可能性。
该插件将每个语句与重写规则进行匹配:
- 计算语句摘要哈希值并将其与规则摘要哈希值进行比较。这可能会产生误报,但可以作为快速拒绝测试。
- 如果语句摘要哈希值与任何模式摘要哈希值匹配,则将语句的规范形式(语句摘要)与匹配规则模式的规范形式进行匹配。
- 如果规范语句与规则匹配,则比较语句和模式中的文字值。模式中的
?
字符匹配语句中的任何文字值。如果语句准备了一个语句,则模式中的?
也匹配语句中的?
。否则,相应的文字值必须相同。
如果多个规则匹配一个语句,则插件使用哪个规则重写语句是不确定的。
如果模式包含的标记比替换多,插件会丢弃多余的数据值。如果模式包含的标记比替换少,这将是一个错误。当加载规则表时,插件会注意到这一点,将错误消息写入规则行的message
列以传达问题,并将Rewriter_reload_error
状态变量设置为ON
。
重写预处理语句
预处理语句在解析时(即在准备时)重写,而不是在稍后执行时。
预处理语句与非预处理语句的区别在于,它们可能包含?
字符作为参数标记。要匹配预处理语句中的?
,Rewriter
模式必须在相同位置包含?
。假设重写规则具有以下模式:
SELECT ?, 3
以下表格显示了几个预处理SELECT
语句以及规则模式是否匹配它们。
预处理语句 | 匹配语句的模式是否匹配 |
PREPARE s AS 'SELECT 3, 3' |
是 |
PREPARE s AS 'SELECT ?, 3' |
是 |
PREPARE s AS 'SELECT 3, ?' |
否 |
PREPARE s AS 'SELECT ?, ?' |
否 |
重写插件操作信息
Rewriter
插件通过几个状态变量提供有关其操作的信息:
mysql> SHOW GLOBAL STATUS LIKE 'Rewriter%'; +-----------------------------------+-------+ | Variable_name | Value | +-----------------------------------+-------+ | Rewriter_number_loaded_rules | 1 | | Rewriter_number_reloads | 5 | | Rewriter_number_rewritten_queries | 1 | | Rewriter_reload_error | ON | +-----------------------------------+-------+
有关这些变量的描述,请参阅 Section 7.6.4.3.4, “Rewriter Query Rewrite Plugin Status Variables”。
当通过调用flush_rewrite_rules()
存储过程加载规则表时,如果某个规则出现错误,则CALL
语句会产生错误,并且插件将Rewriter_reload_error
状态变量设置为ON
:
mysql> CALL query_rewrite.flush_rewrite_rules(); ERROR 1644 (45000): Loading of some rule(s) failed. mysql> SHOW GLOBAL STATUS LIKE 'Rewriter_reload_error'; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Rewriter_reload_error | ON | +-----------------------+-------+
在这种情况下,检查rewrite_rules
表中具有非NULL
message
列值的行,以查看存在哪些问题。
重写插件使用字符集
当rewrite_rules
表加载到Rewriter
插件中时,插件使用当前全局值的character_set_client
系统变量解释语句。如果随后更改了全局character_set_client
值,则必须重新加载规则表。
客户端必须具有与加载规则表时全局值相同的会话character_set_client
值,否则该客户端的规则匹配将无法工作。
原文:
dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin-reference.html
7.6.4.3 Rewriter
查询重写插件参考
以下讨论作为与Rewriter
查询重写插件相关的这些元素的参考:
query_rewrite
数据库中的Rewriter
规则表Rewriter
过程和函数Rewriter
系统和状态变量
7.6.4.3.1 Rewriter
查询重写插件规则表
query_rewrite
数据库中的rewrite_rules
表为Rewriter
插件提供了规则的持久存储,用于决定是否重写语句。
用户通过修改存储在此表中的规则集与插件进行通信。插件通过设置表的message
列向用户传递信息。
注意
通过flush_rewrite_rules
存储过程将规则表加载到插件中。除非在最近的表修改后调用了该过程,否则表内容不一定对应插件正在使用的规则集。
rewrite_rules
表具有以下列:
id
规则 ID。此列是表的主键。您可以使用 ID 唯一标识任何规则。pattern
指示规则匹配语句模式的模板。使用?
表示匹配数据值的参数标记。pattern_database
用于匹配语句中未限定表名的数据库。语句中的限定表名仅在默认数据库与pattern_database
相同且表名相同时,才与模式中的限定名称匹配。语句中的未限定表名仅在默认数据库与pattern_database
相同且表名相同时,才与模式中的未限定名称匹配。replacement
指示如何重写与pattern
列值匹配的语句的模板。使用?
表示匹配数据值的参数标记。在重写的语句中,插件使用replacement
中的?
参数标记,使用与pattern
中相应标记匹配的数据值进行替换。enabled
是否启用规则。加载操作(通过调用flush_rewrite_rules()
存储过程执行)仅在此列为YES
时,将规则从表加载到Rewriter
内存缓存中。
此列使得可以在不移除规则的情况下停用规则:将列设置为非YES
的值,并重新加载表到插件中。message
插件使用此列与用户进行通信。如果在将规则表加载到内存时没有发生错误,则插件将message
列设置为NULL
。非NULL
值表示错误,列内容为错误消息。错误可能发生在以下情况下:
- 要么模式,要么替换是产生语法错误的不正确 SQL 语句。
- 替换包含比模式更多的
?
参数标记。
- 如果发生加载错误,插件还会将
Rewriter_reload_error
状态变量设置为ON
。 pattern_digest
此列用于调试和诊断。如果在将规则表加载到内存时存在该列,则插件将使用模式摘要更新它。如果您试图确定某个语句未能重写的原因,此列可能会有用。normalized_pattern
此列用于调试和诊断。如果在将规则表加载到内存时存在该列,则插件将使用模式的规范形式更新它。如果您试图确定某个语句未能重写的原因,此列可能会有用。
7.6.4.3.2 Rewriter 查询重写插件的过程和函数
Rewriter
插件操作使用一个存储过程,将规则表加载到其内存缓存中,并使用一个辅助可加载函数。在正常操作下,用户只调用存储过程。该函数旨在由存储过程调用,而不是直接由用户调用。
flush_rewrite_rules()
此存储过程使用load_rewrite_rules()
函数将rewrite_rules
表的内容加载到Rewriter
内存缓存中。
调用flush_rewrite_rules()
意味着COMMIT
。
在修改规则表后调用此存储过程,使插件从新表内容更新其缓存。如果发生任何错误,插件将为表中适当规则行设置message
列,并将Rewriter_reload_error
状态变量设置为ON
。load_rewrite_rules()
此函数是由flush_rewrite_rules()
存储过程使用的辅助程序。
7.6.4.3.3 Rewriter 查询重写插件系统变量
Rewriter
查询重写插件支持以下系统变量。仅当安装了插件时才可用这些变量(请参阅第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。
rewriter_enabled
系统变量 | rewriter_enabled |
范围 | 全局 |
动态 | 是 |
SET_VAR 提示适用 |
否 |
类型 | 布尔值 |
默认值 | ON |
有效值 | OFF |
- 是否启用了
Rewriter
查询重写插件。 rewriter_enabled_for_threads_without_privilege_checks
引入版本 | 8.0.31 |
系统变量 | rewriter_enabled_for_threads_without_privilege_checks |
作用域 | 全局 |
动态 | 是 |
SET_VAR 提示适用 |
否 |
类型 | 布尔值 |
默认值 | ON |
有效值 | OFF |
- 是否应用于执行时禁用权限检查的复制线程的重写。如果设置为
OFF
,则跳过此类重写。需要SYSTEM_VARIABLES_ADMIN
权限或SUPER
权限来设置。
如果rewriter_enabled
为OFF
,则此变量无效。 rewriter_verbose
系统变量 | rewriter_verbose |
作用域 | 全局 |
动态 | 是 |
SET_VAR 提示适用 |
否 |
类型 | 整数 |
- 供内部使用。
7.6.4.3.4 Rewriter 查询重写插件状态变量
Rewriter
查询重写插件支持以下状态变量。仅当插件已安装时才可用(请参阅第 7.6.4.1 节,“安装或卸载 Rewriter 查询重写插件”)。
Rewriter_number_loaded_rules
成功从rewrite_rules
表加载到内存中供Rewriter
插件使用的重写插件重写规则数量。Rewriter_number_reloads
rewrite_rules
表被加载到Rewriter
插件使用的内存缓存中的次数。Rewriter_number_rewritten_queries
Rewriter
查询重写插件自加载以来重写的查询次数。Rewriter_reload_error
上次将rewrite_rules
表加载到Rewriter
插件使用的内存缓存中时是否发生错误。如果值为OFF
,则未发生错误。如果值为ON
,则发生了错误;请检查rewriter_rules
表的message
列以获取错误消息。
7.6.5 ddl_rewriter 插件
7.6.5.1 安装或卸载 ddl_rewriter
7.6.5.2 ddl_rewriter 插件选项
MySQL 8.0.16 及更高版本包括一个ddl_rewriter
插件,该插件在服务器解析和执行之前修改接收到的CREATE TABLE
语句。该插件删除ENCRYPTION
、DATA DIRECTORY
和INDEX DIRECTORY
子句,这在从加密数据库或将表存储在数据目录之外的数据库创建的 SQL 转储文件中恢复表时可能会有所帮助。例如,该插件可以使这些转储文件能够恢复到未加密实例或在路径在数据目录之外不可访问的环境中。
在使用ddl_rewriter
插件之前,请根据第 7.6.5.1 节“安装或卸载 ddl_rewriter”中提供的说明进行安装。
ddl_rewriter
在服务器解析之前检查接收到的 SQL 语句,根据这些条件对其进行重写:
ddl_rewriter
仅考虑CREATE TABLE
语句,只有当它们是独立的语句,并且出现在输入行的开头或准备语句文本的开头时。ddl_rewriter
不考虑存储程序定义中的CREATE TABLE
语句。语句可以跨越多行。- 在进行重写考虑的语句中,以下子句的实例将被重写,并且每个实例将被单个空格替换:
ENCRYPTION
DATA DIRECTORY
(在表和分区级别)INDEX DIRECTORY
(在表和分区级别)
- 重写不依赖于大小写。
如果ddl_rewriter
重写了一个语句,它会生成一个警告:
mysql> CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data'; Query OK, 0 rows affected, 1 warning (0.03 sec) mysql> SHOW WARNINGS\G *************************** 1\. row *************************** Level: Note Code: 1105 Message: Query 'CREATE TABLE t (i INT) DATA DIRECTORY '/var/mysql/data'' rewritten to 'CREATE TABLE t (i INT) ' by a query rewrite plugin 1 row in set (0.00 sec)
如果启用了一般查询日志或二进制日志,服务器会在任何ddl_rewriter
重写后将语句写入其中。
安装后,ddl_rewriter
会为跟踪插件内存使用情况暴露性能模式memory/rewriter/ddl_rewriter
。请参阅第 29.12.20.10 节“内存摘要表”