MySQl的繫結變數特性
從MySQL 4.1版本開始,就支援伺服器端的繫結變數(preparedstatement),這大大提高了客戶端和伺服器端資料傳輸的效率。你若使用一個支援新協議的客戶端,如MySQL CAPI,就可以使用繫結變數功能了。另外,Java和.NET的也都可以使用各自的客戶端Connector/J和Connector/NET來使用繫結變數。最後,還有一個SQL介面用於支援繫結變數,後面我們將討論這個(這裡容易引起困擾)。
當建立一個繫結變數SQL時,客戶端向伺服器傳送了一個SQL語句的原型。伺服器端收到這個SQL語句框架後,解析並存儲這個SQL語句的部分執行計劃,返回給客戶端一個SQL語句處理控制代碼。以後每次執行這類查詢,客戶端都指定使用這個控制代碼。
繫結變數的SQL,使用問號標記可以接收引數的位置,當真正需要執行具體査詢的時候,則使用具體值代替這些問號。例如,下面是一個繫結變數的SQL語句:
INSERT INTO tbl(col1, col2, col3) VALUES (?, ?, ?);
可以通過向伺服器端傳送各個問號的取值和這個SQL的控制代碼來執行一個具體的査詢。反覆使用這樣的方式執行具體的査詢,這正是繫結變數的優勢所在。具體如何傳送取值引數和SQL控制代碼,則和各個客戶端的程式語言有關。使用Java和.NET的MySQL聯結器就是一種辦法。很多使用MySQLC語言連結庫的客戶端可以提供類似的介面,需要根據使用的程式語言的文件來了解如何使用繫結變數。
因為如下的原因,MySQL在使用繫結變數的時候可以更高效地執行大量的重複語句:
- 在伺服器端只需要解析一次SQL語句。
- 在伺服器端某些優化器的工作只需要執行一次,因為它會快取一部分的執行計劃。
- 以二進位制的方式只發送引數和控制代碼,比起每次都發送ASCII碼文字效率更高,一個二進位制的日期欄位只需要三個位元組,但如果是ASCII碼則需要十個位元組。不過最大的節省還是來自於BLOB和TEXT欄位,繫結變數的形式可以分塊傳輸,而無須一次性傳輸。二進位制協議在客戶端也可能節省很多記憶體,減少了網路開銷,另外,還節省了將資料從儲存原始格式轉換成文字格式的開銷。
- 僅僅是引數——而不是整個查詢語句——需要傳送到伺服器端,所以網路開銷會更小。
- MySQL在儲存引數的時候,直接將其存放到快取中,不再需要在記憶體中多次複製。
繫結變數相對也更安全。無須在應用程式中處理轉義,一則更簡單了,二則也大大減少 了SQL注入和攻擊的風險。(任何時候都不要信任使用者輸入,即使是使用繫結變數的時候。)
可以只在使用繫結變數的時候才使用二進位制傳輸協議。如果使用普通的mysql_query()介面則不會使用二進位制傳輸協議。還有一些客戶端讓你使用繫結變數,先發送帶引數的繫結SQL,然後傳送變數值,但是實際上,這些客戶端只是模擬了繫結變數的介面,最後還是會直接用具體值代替引數後,再使用mysql_query()傳送整個査詢語句。
1.繫結變數的優化
對使用繫結變數的SQL,MySQL能夠快取其部分執行計劃,如果某些執行計劃需要根據傳入的引數來計算時,MySQL就無法快取這部分的執行計劃。根據優化器什麼時候工作,可以將優化分為三類。Mysql5.6版本之前,下面的三點是適用的。
在準備階段
伺服器解析SQL語句,移除不可能的條件,並且重寫子査詢。
在第一次執行的時候
如果可能的話,伺服器先簡化巢狀迴圈的關聯,並將外關聯轉化成內關聯。
在每次SQL語句執行時
伺服器做如下事情:
-
- 過濾分割槽。
- 如果可能的話,儘量移除COUNT()、MIN()和MAX()。
- 移除常數表示式。
- 檢測常量表。
- 做必要的等值傳播。
- 分析和優化ref、range和索引優化等訪問資料的方法。
- 優化關聯順序。
理論上,有些優化只需要做一次,但實際上,上面的操作還是都會被執行。
2.SQL介面的繫結變數
在4.1和更新的版本中,MySQL支援了SQL介面的繫結變數。不使用二進位制傳輸協議也可以直接以SQL的方式使用繫結變數。下面案例展示瞭如何使用SQL介面的繫結變數:
mysql> SET @sql := 'SELECT actor_id, first_name, last_name -> FROM sakila.actor WHERE first_name = ?'; mysql> PREPARE stmt_fetch_actor FROM @sql; mysql> SET @actor_name := 'Penelope'; mysql> EXECUTE stmt_fetch_actor USING @actor_name; +----------+------------+-----------+ | actor_id | first_name | last_name | +----------+------------+-----------+ | 1 | PENELOPE | GUINESS | | 54 | PENELOPE | PINKETT | | 104 | PENELOPE | CRONYN | | 120 | PENELOPE | MONROE | +----------+------------+-----------+ mysql> DEALLOCATE PREPARE stmt_fetch_actor;
當伺服器收到這些SQL語句後,先會像一般客戶端的連結庫一樣將其翻譯成對應的操作。這意味著你無須使用二進位制協議也可以使用繫結變數。
正如你看到的,比起直接編寫的SQL語句,這裡的語法看起來有一些怪怪的。那麼,這種寫法實現的繫結變數到底有什麼優勢呢?
最主要的用途就是在儲存過程中使用。在MySQL5.0版本中,就可以在儲存過程中使用繫結變數,其語法和前面介紹的SQL介面的繫結變數類似。這意味,可以在儲存過程中構建並執行“動態”的SQL語句,這裡的“動態”是指可以通過靈活地拼接字串等引數構建SQL語句。例如,下面的示例儲存過程中可以針對某個資料庫執行OPTIMIZETABLE的操作:
DROP PROCEDURE IF EXISTS optimize_tables; DELIMITER // CREATE PROCEDURE optimize_tables(db_name VARCHAR(64)) BEGIN DECLARE t VARCHAR(64); DECLARE done INT DEFAULT 0; DECLARE c CURSOR FOR SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = db_name AND TABLE_TYPE = 'BASE TABLE'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN c; tables_loop: LOOP FETCH c INTO t; IF done THEN LEAVE tables_loop; END IF; SET @stmt_text := CONCAT("OPTIMIZE TABLE ", db_name, ".", t); PREPARE stmt FROM @stmt_text; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP; CLOSE c; END// DELIMITER ;
可以這樣呼叫這個儲存過程:
mysql> CALL optimize_tables('sakila');
另一種實現儲存過程中迴圈的辦法是:
REPEAT FETCH c INTO t; IF NOT done THEN SET @stmt_text := CONCAT("OPTIMIZE TABLE ", db_name, ".", t); PREPARE stmt FROM @stmt_text; EXECUTE stmt; DEALLOCATE PREPARE stmt; END IF; UNTIL done END REPEAT;
這兩種迴圈結構最重要的區別在於:REPEAT會為每個迴圈檢査兩次迴圈條件。在這個例子中,因為迴圈條件檢査的是一個整數判斷,並不會有什麼效能問題,如果迴圈的判斷條件非常複雜的話,則需要注意這兩者的區別。
像這樣使用SQL介面的繫結變數拼接表名和庫名是很常見的,這樣的好處是無須使用任何引數就能完成SQL語句。而庫名和表名都是關鍵字,在二進位制協議的繫結變數中是不能將這兩部分引數化的。另一個經常需要動態設定的就是LIMIT子句,因為二進位制協議中也無法將這個值引數化。
另外,編寫儲存過程時,SQL介面的繫結變數通常可以很大程度地幫助我們除錯繫結變數,如果不是在儲存過程中,SQL介面的繫結變數就不是那麼有用了。因為SQL介面的繫結變數,它既沒有使用二進位制傳輸協議,也沒有能夠節省頻寬,相反還總是需要增加至少一次額外網路傳輸才能完成一次査詢。所有隻有在某些特殊的場景下SQL介面的繫結變數才有用,比如當SQL語句非常非常長,並且需要多次執行的時候。
3.繫結變數的限制
關於繫結變數的一些限制和注意事項如下:
- 繫結變數是會話級別的,所以連線之間不能共用繫結變數控制代碼。同樣地,一旦連線斷開,則原來的控制代碼也不能再使用了。(連線池和持久化連線可以在一定程度上緩解這個問題。)
- 在MySQL5.1版本之前,繫結變數的SQL是不能使用査詢快取的。
- 並不是所有的時候使用繫結變數都能獲得更好的效能。如果只是執行一次SQL,那麼使用繫結變數方式無疑比直接執行多了一次額外的準備階段消耗,而且還需要一次額外的網路開銷。(要正確地使用繫結變數,還需要在使用完成後,釋放相關的資源。)
- 當前版本下,還不能在儲存函式中使用繫結變數(但是儲存過程中可以使用)。
- 如果總是忘記釋放繫結變數資源,則在伺服器端很容易發生資源“洩漏”。繫結變數SQL總數的限制是一個全侷限制,所以某一個地方的錯誤可能會對所有其他的執行緒都產生影響。
- 有些操作,如BEGIN,無法在繫結變數中完成。
不過使用繫結變數最大的障礙可能是:它是如何實現以及原理是怎樣的,這兩點很容易讓人困惑。有時,很難解釋如下三種繫結變數型別之間的區別是什麼:
客戶端模擬的繫結變數
客戶端的驅動程式接收一個帶引數的SQL,再將指定的值帶入其中,最後將完整的査詢傳送到伺服器端。
伺服器端的繫結變數
客戶端使用特殊的二進位制協議將帶引數的字串傳送到伺服器端,然後使用二進位制協議將具體的引數值傳送給伺服器端並執行。
SQL介面的繫結變數
客戶端先發送一個帶引數的字串到伺服器端,這類似於使用PREPARE的SQL語句,然後傳送設定引數的SQL,最後使用EXECUTE來執行SQL。所有這些都使用普通的文字傳輸協議。
作者:小家電維修
相見有時,後會無期。