这个功能一年左右之前就以知晓,应该是5.7的高版本中。今天难得有兴致测试、随之也就总结一下。
前言:
一般来说,我们都会让开发自己去改sql。这样需要重启应用,单节点不可避免有或多或少的停服时间。同事主动权也就不在自己手里。
MySQL5.7某个版本开始有查询改写这个功能。所为查询改写,就是某种SQL写的非常不友好场景下,在MySQL服务端通过查询改写,将这SQL改写成相对友好的形式。
Rewriter
需要先安装服务端插件Rewriter。
查询改写特性在mysql5.7中开始出现。
8.0.12之前只能改写SELECT语句。
8.0.12开始,可以改写SELECT, INSERT, REPLACE, UPDATE, and DELETE.
需要记住的一点是,一旦安装,势必会给系统增加一定负担,即便不启用它。所以如果不打算用该特性,不要安装。
安装
需要做的事,有以下几件。
- 首先创建一个query_rewrite库, 库中一个rewrite_rules表。用来保存定义的改写规则。
- 安装rewriter插件,实现函数。
- 创建一个函数load_rewrite_rules的自定义函数,其实现为rewriter共享库。
- 创建一个存储过程,存储过程里面定义刷新查询改写缓存的方法。
- 从查询缓存集中移除所有查询缓存。(8.0移除了QueryCache,自然也就没有这个。)
安装就是执行一个脚本。脚本里面封装了需要做的事。
说了这么多,非全是废话,主要为了引出下列脚本。只要执行这个脚本,即可完成上面的几个步骤。文件在$MysqlHomeDir/share目录下。
mysql -udba_yix -p < /usr/local/mysql/share/install_rewriter.sql
顺便说一个卸载。其文件内容,不过是做了大致相反的事。有兴趣可以自己去看,实际上就三行内容。
mysql -udba_yix -p < /usr/local/mysql/share/uninstall_rewriter.sql
启用并测试
安装好过后,默认就是启用的。
增加改写规则
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement) VALUES('SELECT ?', 'SELECT ? + 1');
执行以下语句、发现没有生效,原因是要将这个新插入的规则生效。这里就用到了,上面脚本中的定义的一个存储过程。这个和 update权限表,要flush privileges是一个道理。
CALL query_rewrite.flush_rewrite_rules();
刷新过后发现,查询改写生效了。
再次查询查询规则,发现不一样了。
说明一下。?是一个占位符。匹配数据的值,并不匹配关键字、标识符。? 符不能有 单双引号包裹。同样必须的是,? 符号,最好一一对应上。
案列2:将删除语句改写成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();
发现报了错。如果有报错,需要结合 query_rewrite.rewrite_rules表中的message字段查看原因。
这就是我们上面提到的在8.0.12之前只能改写select。
灵活配置
如果发现某个规则,需要临时关闭,可使用修改语句将其关闭。
UPDATE query_rewrite.rewrite_rules SET enabled = 'NO' WHERE id = 1; CALL query_rewrite.flush_rewrite_rules();
重启则参考下面语句。
UPDATE query_rewrite.rewrite_rules SET enabled = 'YES' WHERE id = 1; CALL query_rewrite.flush_rewrite_rules();
同样的语句在不同库中不通对待。
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();
如果使用下述查询来匹配上述规则
SELECT * FROM users WHERE appdb.id = id_value;
SELECT * FROM users WHERE id = id_value;
则重写器(rewriter)会使用第一条规则匹配第一条SQL。第二条规则匹配第二条SQL(前提是默认的数据库是appdb)。
重写器如何工作的?
重写器插件使用语句的内容以及内容计算出的哈希值来匹配传入语句和重写规则。
max_digest_length 系统变量决定了用以计算语句的buffersie.较小的值使用较少的内存,但会增加较长语句与相同摘要值冲突的可能性(hash碰撞)。
如果多个规则与一个语句匹配,那么重写器用来重写语句的是不确定的。
如果匹配模式中(被替换的)?多余替换(replacement)中的?,则重写器会忽略多余的数据。反之,会报错(Rewriter_reload_error状态变量会被置为on)。所以最好要保持两边的?占位符个数相等。
重写prepare 语句需要注意的是,由于prepare中有 ? 。需要在模式中和其对应上。如一个pattern:
SELECT ?, 3
几个prepare匹配情况如下:
Prepared Statement
|
Whether Pattern Matches 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
|
重写器状态统计信息
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 | +-----------------------------------+-------+
需要注意的一点:重写器使用的字符集。如果全局变量character_set_client更改,查询规则要重新reload。正常情况,我们会在client块配置它。所以在jdbc连接中,不要指定或者不要定义错。即可。
IT相关技术交流群:472983519 (Java,PHP,运维、开发、架构师)
各类技术电子书获取群:814183658 (需要提供PDF书籍、电子文档等相关链接)
DBA专用群:323842783 (Oracle OCM,MySQL内核探秘者、mongodb、redis等)
(为防止广告主,加群还请备注475982055)