5.5.4.2 使用重写器查询重写插件

要启用或禁用插件,请启用或禁用rewriter_enabled系统变量。默认情况下,安装时会启用Rewriter插件(请参阅第 5.5.4.1 节“安装或卸载重写器查询重写插件”)。要显式设置初始插件状态,可以在服务器启动时设置变量。例如,要在选项文件中启用插件,请使用以下几行:

[mysqld]
rewriter_enabled=ON

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

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

假设启用了Rewriter插件,它将检查并可能修改服务器收到的每个SELECT语句。该插件根据其在内存中的重写规则高速缓存来确定是否重写语句,这些高速缓存是从query_rewrite数据库中的rewrite_rulestable 加载的。

添加重写规则

要为Rewriter插件添加规则,请在rewrite_rulestable 中添加行,然后调用flush_rewrite_rules()存储过程以将 table 中的规则加载到插件中。下面的示例创建一个简单的规则来匹配选择单个 Literals 值的语句:

INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('SELECT ?', 'SELECT ? + 1');

产生的 table 内容如下所示:

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_rulestable 不足以使Rewriter插件使用该规则。您必须调用flush_rewrite_rules()才能将 table 内容加载到插件内存缓存中:

mysql> CALL query_rewrite.flush_rewrite_rules();

Tip

如果您的重写规则似乎无法正常工作,请通过调用flush_rewrite_rules()确保已重新加载规则 table。

当插件从规则 table 中读取每个规则时,它会从模式和摘要哈希值中计算出标准化(语句摘要)形式,并使用它们来更新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: 46b876e64cd5c41009d91c754921f1d4
normalized_pattern: select ?

有关语句摘要,规范化语句和摘要哈希值的信息,请参见第 25.10 节“性能模式语句摘要”

如果由于某些错误而无法加载规则,则调用flush_rewrite_rules()会产生错误:

mysql> CALL query_rewrite.flush_rewrite_rules();
ERROR 1644 (45000): Loading of some rule(s) failed.

发生这种情况时,插件会将错误消息写入规则行的message列以传达问题。检查rewrite_rulestable 中是否有非NULL message列值的行,以查看存在什么问题。

模式使用与准备好的语句相同的语法(请参见第 13.5.1 节“ PREPARE 语句”)。在模式模板中,?个字符用作与数据值匹配的参数标记。参数标记只能用于应显示数据值的地方,而不能用于 SQL 关键字,标识符等。 ?字符不应用引号引起来。

与模式一样,替换字符可以包含?个字符。对于与模式模板匹配的语句,插件将对其进行重写,使用与模式中相应标记匹配的数据值替换替换中的?参数标记。结果是一个完整的语句字符串。该插件要求服务器解析它,并将结果作为重写语句的 table 示返回给服务器。

添加和加载规则后,根据语句是否与规则模式匹配来检查是否发生重写:

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语句,不会进行任何重写,而对于第二个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

要启用或禁用现有规则,请修改其enabled列,然后将 table 重新加载到插件中。要禁用规则 1:

UPDATE query_rewrite.rewrite_rules SET enabled = 'NO' WHERE id = 1;
CALL query_rewrite.flush_rewrite_rules();

这使您可以停用规则而不将其从 table 中删除。

重新启用规则 1:

UPDATE query_rewrite.rewrite_rules SET enabled = 'YES' WHERE id = 1;
CALL query_rewrite.flush_rewrite_rules();

rewrite_rulestable 包含pattern_database列,Rewriter用于匹配不具有数据库名的 table 名:

  • 如果相应的数据库名和 table 名相同,则语句中的合格 table 名与模式中的合格名匹配。

  • 仅当默认数据库与pattern_database相同且 table 名相同时,语句中不合格的 table 名才会与模式中的不合格名匹配。

假设名为appdb.users的 table 具有名为id的列,并且应用程序应使用以下形式之一的查询从 table 中选择行,其中只有appdb是默认数据库,才可以使用第二种形式:

SELECT * FROM users WHERE appdb.id = id_value;
SELECT * FROM users WHERE id = id_value;

还假设id列重命名为user_id(也许必须修改 table 以添加另一种 ID 类型,并且有必要更具体地指出id列代 table 哪种 ID 类型)。

该更改意味着应用程序必须在WHERE子句中引用user_id而不是id。但是,如果有一些旧应用程序无法编写以更改它们生成的SELECT查询,则它们将不再正常工作。 Rewriter插件可以解决此问题。要匹配和重写语句(无论它们是否符合 table 名),请添加以下两个规则并重新加载规则 table:

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使用第一条规则来匹配使用限定 table 名的语句。它使用第二个字符串来匹配使用非限定名称的语句,但前提是默认数据库为appdb(pattern_database中的值)。

语句匹配的工作方式

Rewriter插件使用语句摘要和摘要哈希值来分阶段将传入语句与重写规则进行匹配。 max_digest_length系统变量确定用于计算语句摘要的缓冲区的大小。较大的值使摘要的计算可以区分较长的语句。较小的值使用较少的内存,但增加了较长的语句与相同的摘要值冲突的可能性。

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

  • 计算语句摘要哈希值,并将其与规则摘要哈希值进行比较。这会产生误报,但可以作为快速拒绝测试。

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

  • 如果规范化的语句与规则匹配,请比较该语句和模式中的 Literals 值。模式中的?字符与语句中的任何 Literals 值匹配。如果该语句准备SELECT语句,则模式中的?也将与该语句中的?匹配。否则,相应的 Literals 必须相同。

如果有多个规则与一条语句匹配,则该插件用于重写该语句是不确定的。

如果某个模式包含的标记多于替换标记,则该插件会丢弃多余的数据值。如果模式包含的标记少于替换的标记,则为错误。插件在加载规则 table 时会注意到这一点,将错误消息写入规则行的message列以传达问题,并将Rewriter_reload_error status 变量设置为ON

重写准备好的语句

准备好的语句在解析时(即准备好的时候)被重写,而不是在稍后执行时被重写。

准备的语句与未准备的语句的不同之处在于它们可以包含?个字符作为参数标记。要与已准备好的语句中的?匹配,Rewriter模式必须在同一位置包含?。假设重写规则具有以下模式:

SELECT ?, 3

下 table 显示了几个准备好的SELECT语句以及规则模式是否与它们匹配。

Prepared Statement模式是否匹配语句
PREPARE s AS 'SELECT 3, 3'Yes
PREPARE s AS 'SELECT ?, 3'Yes
PREPARE s AS 'SELECT 3, ?'No
PREPARE s AS 'SELECT ?, ?'No
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    |
+-----------------------------------+-------+

有关这些变量的说明,请参见第 5.5.4.3.4 节“重写器查询重写插件状态变量”

通过调用flush_rewrite_rules()存储过程加载规则 table 时,如果某些规则发生错误,则CALL语句将产生错误,并且插件会将Rewriter_reload_error status 变量设置为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_rulestable 中具有非NULL message列值的行,以查看存在哪些问题。

Rewriter 插件对字符集的使用

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

Client 端必须具有与加载规则 table 时的全局值相同的会话character_set_client值,否则规则匹配将不适用于该 Client 端。