MySQL數據庫的預處理(prepared statement)性能測試

1、預處理幹了什麼

        當我們提交一條數據庫語句時,語句到達數據庫服務那邊,數據庫服務需要解析這條sql語句,比如說語法檢查,查詢條件先後優化,然後才執行。對於預處理,簡單來說就是把客戶端與數據庫服務原本一次交互的分成兩次。首先,提交數據庫語句,讓數據庫服務先解析這條語句。其次,提交參數,調用語句並執行。這樣對於多次重複執行的語句來說,可以提交併解析一次數據庫語句就可以了,然後不斷的調用剛剛解析過得語句並執行。這樣就省去了多次解析同一條語句的時間。從而達到提高效率的目的。

        預處理語句支持佔位符(place holder),通過綁定佔位符的方式提交參數。一個非常重要的一點是,能與佔位符綁定的只能是值,而不能是sql語句的一些關鍵詞。例如語句:“select * from student where student.id = ?”。如果放入佔位符(?)中的是“1 or 1=1”,那麼“1 or 1=1”就會被當成一個值,即用``符號包括起來,最終這條非法的語句就出錯了。從而達到放sql注入的漏洞(sql injestion)。

       預處理機制主要的三步驟:

       1、將語句進行預處理

       2、執行語句

       3、析構掉預處理語句。

 

2、關於`performance_schema`.`prepared_statements_instances` 表的介紹

         運行sql腳本:show global variable like ‘%prepare%’。 可以看到一個叫‘performance_schema_max_prepared_statement_instances的系統變量。其值爲0表示不啓用預處理語句性能數據記錄表`performance_schema`.`prepared_statements_instances`;-1表示記錄的數量動態處理;其他正整數值則表示performance_schema_max_prepared_statement_instances記錄的最大條數。

        表`performance_schema`.`prepared_statements_instances`又是什麼呢?它是用來記錄預處理語句的一些基本信息和性能數據。比如預處理語句的ID,預處理語句的名字,預處理語句的具體語句內容,預處理語句被執行的次數,每次執行耗時,每條預處理語句所屬的線程id等。當我們創建一條預處理語句時,就會插入一條數據到這張表裏。預處理語句是基於連接的,連接斷開,則預處理語句自動刪除。但`performance_schema`.`prepared_statements_instances`表是全局的,它與數據庫連接沒關係。有了這些數據,我們就可以知道,1、代碼中執行的語句是否真的做了預處理,2、通過了解預處理語句的執行情況來決定業務中是否需要把一個語句進行預處理。

 

3、qt prepare函數說明

        根據我自己本身的項目需求,這次測試的客戶端代碼使用的是Qt。這裏記錄一個關鍵的函數:QSqlQuery類的prepare函數。調用prepare函數即是向數據庫提交一個創建預處理語句的命令。意味着調用期間,是會與數據庫服務進行一次交互的。需要注意的是,當同一個QSqlQuery類對象調用第二次prepare時,會將第一次調用prepare創建的預處理語句刪除掉,然後再創建一條預處理語句,即便是這兩條預處理語句是一模一樣的。在調用QSqlQuery的exec函數時,也會將QSqlQuery先前創建的預處理語句刪除掉。所以在查詢結束,關閉掉連接,或者查詢又執行了其他語句,從而導致`performance_schema`.`prepared_statements_instances`表沒有相關預處理語句的記錄,就會誤認爲預處理語句創建失敗。其實Qt的這種做法,也省去了要我們人爲的刪除預處理語句。

 

4、實驗猜想

        常規執行的語句和預處理後執行的語句不同點在於,在多次執行的情況下,預處理語句只需解析一次sql語句,而之後多花時間在傳輸參數和綁定參數上。預處理語句在返回結果時,使用的是二進制傳輸協議,而普通語句使用的是文本格式的傳輸協議。因此我們做出以下猜想並驗證。

        1、如果執行的是簡單語句,那麼普通執行和預處理執行性能上差別不大。預處理語句在重複執行復雜的語句情況下才展現出優勢。

        2、在查詢結果集是大數據量的情況下,預處理語句會展現出性能優勢。

 

5、實驗數據記錄

          

序號 是否預處理 語句 是否遠程數據庫 返回數據量 每次實驗語句執行總次數 三次實驗平均總耗時/單位毫秒
1 select * from task where task.taskId in (?) 1000 1000 69822
2 select * from task where task.taskId in (arr) 1000 1000 66778
3 select * from task where task.taskId = ? 1 1000 1260
4 select * from task where task.taskId = id 1 1000 951
5 select * from task a LEFT JOIN task_file b ON a.taskId = b.task_id where a.taskName like '%s%' and b.file_id > 100000 and b.file_id < 200000  and a.taskId = ?"; 2 1000 2130
6 select * from task a LEFT JOIN task_file b ON a.taskId = b.task_id where a.taskName like '%s%' and b.file_id > 100000 and b.file_id < 200000  and a.taskId = 32327"; 2 1000 1480
7 select * from task where task.taskId in (?) 1000 1000 57051
8 select * from task where task.taskId in (arr) 1000 1000 56235
9 select * from task where task.taskId = ? 1 1000 217
10 select * from task where task.taskId = id 1 1000 204
11 select * from task a LEFT JOIN task_file b ON a.taskId = b.task_id where a.taskName like '%s%' and b.file_id > 100000 and b.file_id < 200000  and a.taskId = ?"; 2 1000 366
12 select * from task a LEFT JOIN task_file b ON a.taskId = b.task_id where a.taskName like '%s%' and b.file_id > 100000 and b.file_id < 200000  and a.taskId = 32327"; 2 1000 380

 

6、結論

         實驗的數據結果和我預期的相差有點兒大,但經過反覆檢查測試代碼和測試過程,確認測試本身應該沒有問題。尊重實驗數據,我們得出以下結論:

        1、通過實驗5和實驗6對比,實驗11和實驗12對比,可得猜想1是錯誤的。結論應該是:MySQL預處理和常規查詢在簡單語句和複雜語句下,都沒有顯著性的性能差別

        2、通過實驗1和實驗2對比,實驗7和實驗8對比,可得猜想2是錯誤的。結論應該是:MySQL預處理和常規查詢的結果在數據傳輸上沒有顯著性的性能差距。

        3、此外,對比遠程數據庫和本地數據庫實驗數據。可得結論:MySQL數據庫在本地會給數據操作帶來顯著性的性能提高。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章