DBA主宰一切請求,MySQL 查詢重寫
阿新 • • 發佈:2019-01-07
這個功能一年左右之前就以知曉,應該是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,自然也就沒有這個。)
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連線中,不要指定或者不要定義錯。即可。 參看: https://dev.mysql.com/doc/refman/8.0/en/rewriter-query-rewrite-plugin.html IT相關技術交流群:472983519 (Java,PHP,運維、開發、架構師) 各類技術電子書獲取群:814183658 (需要提供PDF書籍、電子文件等相關連結) DBA專用群:323842783 (Oracle OCM,MySQL核心探祕者、mongodb、redis等) (為防止廣告主,加群還請備註475982055)