這個功能一年左右之前就以知曉,應該是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)