文档首页
MySQL 8.4 参考手册
相关文档 下载此手册
PDF (美国信函) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  使用重写查询重写插件

7.6.4.2 使用重写查询重写插件

要启用或禁用插件,请启用或禁用 rewriter_enabled 系统变量。默认情况下,当您安装 Rewriter 插件时,它处于启用状态(参见 第 7.6.4.1 节,“安装或卸载重写查询重写插件”)。要明确设置插件的初始状态,您可以在服务器启动时设置该变量。例如,要在选项文件中启用该插件,请使用以下行

[mysqld]
rewriter_enabled=ON

您也可以在运行时启用或禁用该插件

SET GLOBAL rewriter_enabled = ON;
SET GLOBAL rewriter_enabled = OFF;

假设 Rewriter 插件已启用,它将检查并可能修改服务器接收到的每个可重写语句。该插件根据其在内存中缓存的重写规则(从 query_rewrite 数据库中的 rewrite_rules 表加载)来确定是否要重写语句。

以下语句可进行重写:SELECTINSERTREPLACEUPDATEDELETE

独立语句和准备好的语句都可以进行重写。视图定义或存储程序中出现的语句不能进行重写。

拥有 SKIP_QUERY_REWRITE 权限的用户运行的语句不会被重写,前提是 rewriter_enabled_for_threads_without_privilege_checks 系统变量设置为 OFF(默认值为 ON)。这可用于控制语句和应保持不变的复制语句,例如由 CHANGE REPLICATION SOURCE TO 指定的 SOURCE_USER。这也适用于由 MySQL 客户端程序(包括 mysqlbinlogmysqladminmysqldump)执行的语句;因此,您应将 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_patternpattern_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 表中 message 列值非 NULL 的行,以查看存在哪些问题。

模式使用与预处理语句相同的语法(请参阅 第 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 系统变量确定用于计算语句摘要的缓冲区大小。 较大的值能够计算区分较长语句的摘要。 较小的值使用更少的内存,但会增加较长语句与相同摘要值冲突的可能性。

插件将每个语句与重写规则匹配,如下所示:

  1. 计算语句摘要哈希值,并将其与规则摘要哈希值进行比较。 这可能会出现误报,但可以用作快速拒绝测试。

  2. 如果语句摘要哈希值与任何模式摘要哈希值匹配,则将语句的规范化(语句摘要)形式与匹配规则模式的规范化形式进行匹配。

  3. 如果规范化的语句与规则匹配,则比较语句和模式中的文字值。 模式中的 ? 字符匹配语句中的任何文字值。 如果语句准备一个语句,模式中的 ? 也匹配语句中的 ?。 否则,相应的文字必须相同。

如果多个规则与语句匹配,则插件使用哪个规则来重写语句是非确定性的。

如果模式包含的标记多于替换,则插件会丢弃多余的数据值。 如果模式包含的标记少于替换,则为错误。 插件在加载规则表时会注意到这一点,会将错误消息写入规则行的 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 插件运行信息

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    |
+-----------------------------------+-------+

有关这些变量的描述,请参阅 第 7.6.4.3.4 节,“Rewriter 查询重写插件状态变量”

当您通过调用 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 表中 message 列值非 NULL 的行,以查看存在哪些问题。

Rewriter 插件使用字符集

rewrite_rules 表加载到 Rewriter 插件中时,插件会使用 character_set_client 系统变量的当前全局值来解释语句。 如果全局 character_set_client 值随后发生更改,则必须重新加载规则表。

客户端必须具有与加载规则表时的全局值相同的会话 character_set_client 值,否则该客户端的规则匹配将无法正常工作。