文档主页
MySQL 9.0 参考手册
相关文档 下载此手册
PDF (US Ltr) - 40.0Mb
PDF (A4) - 40.1Mb
Man Pages (TGZ) - 258.2Kb
Man Pages (Zip) - 365.3Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 9.0 参考手册  /  ...  /  使用 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 表中加载的。

以下语句可以重写: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 值,否则该客户端的规则匹配将无法正常工作。