Prepared SQL 效能測試

201432273發表於2022-03-20

一:Prepere Statement 簡介

 prepare statement 即 SQL 預處理。什麼是 SQL 預處理? 普通 SQL 語句執行的邏輯 需要經過 server 層 的 分析器 (圖中圈住的部分) 對 sql 語句進行詞法語法解析、sql 編譯,

這需要一些效能開銷,尤其在一些高併發的場景中可能是效能提升的一個突破點。Prepere Statement 就是幹這個事的,他可以對 SQL 進行預編譯。 檢視 MySQL 官方文件,

有這麼一條說明:

Prepere Statement 適用的場景是,SQL 語句 未發生改變,只是 query 的值發生了改變的情況。通俗的講就是,比如 有這麼一條 SQL insert into t1(name, age) values ( "siri", 18 )

當你要批量執行時,Prepare 方法 可以用 佔位符的方式填充 SQL 值變化的部分,即 insert into t1(name, age) values ( ?, ? ) ,可以由 Prepare 提交給 分析器預編譯。下次再

執行的時候,直接 EXECUTE Statement 佔位符填充,省去了再次 語法解析、編譯的過程,達到一次編譯,多次執行的效果。


那麼,Prepere 在高並場景下,效能能提升多少呢,官方並未給出答案,下面準備實測一下:
Prepared SQL 效能測試

二:Prepere Statement 效能測試

測試環境:(測試 批量 插入效能 )

  • 測試例項:MySQl 5,7
  • 配置: 4 核 8G

網上關於 prepare 效能測試的帖子很少, 於是需要自己寫測試工具。( http://gitlab.xxxxx.com/master/mysql_prepare_test

工具關鍵部分如下:

// Prepare SQL syntax
func PrepareExec(n int, wg *sync.WaitGroup) {
   // NameExec
 
   sqlStr := "insert into user(name,age) values(?,?)"   // 佔位符
   stmt, _ := db.Prepare(sqlStr)  // 開啟 Prepare ,預編譯 SQL 語句
 
   defer stmt.Close()
   defer wg.Done()
 
   for i := 0; i <= n; i++ {
      name := RandString(10)
      _, err := stmt.Exec(name, 20)
      if err != nil {
         fmt.Println(err)
      }
      //rows, _ := ret.RowsAffected()
 
      //record := fmt.Sprintf("RowsAffected: %d", rows)
      //logger.Write(record)
   }
}
 
// NonPrepare SQL syntax
func NonPrepareExec(n int, wg *sync.WaitGroup) {
   // NameExec
   defer wg.Done()
   for i := 0; i <= n; i++ {
      name := RandString(8)
      sqlStr := "insert into user(name,age) values(?,?)"
      _, err := db.Exec(sqlStr, name, 18)    // 未 prepare 處理的普通 SQL
      if err != nil {
         fmt.Println(err)
      }
 
      //rows, _ := ret.RowsAffected()
      //record := fmt.Sprintf("RowsAffected: %d", rows)
      //logger.Write(record)
   }
}

測試結果如下:

  • 4 個執行緒,每個執行緒 100 個 insert:
未使用 prepare:
./prepare_test --prepare=false --t=4 --i=100                          
cost time:409.5343ms
 
使用 prepare:
./prepare_test --prepare=true --t=4 --i=100
cost time:275.1861ms
  • 4 個執行緒,每個執行緒 300 個 insert:
未使用 prepare:
./prepare_test --prepare=false --t=4 --i=300                          
cost time:1.4089236s
 
使用 prepare:
./prepare_test --prepare=true --t=4 --i=300
cost time:791.6015ms

篇幅有限:測試彙總如下

  • --t thread : 執行緒數
  • --i insert : 每個執行緒 insert 語句總數
併發引數 未使用 Prepare 使用 Prepare 效能提升
t=4 i=100 409.5343ms 275.1861ms 32%
t=4 i=300 1.4089236s 791.6015ms 42%
t=4 i=500 2.1703388s 1.129176s 47%
t=8 i=100 629.015ms 297.4847ms 52%
t=8 i=300 3.2628256s 1.67031s 50%
t=8 i=500 3.2897162s 3.2978884s 0%

 
從測試結果看,使用 Prepare 進行 批量插入,和 普通的 sql 相比,效能提升在 30%-50% 之間,但是當超出 例項效能時用不用 Prepare 沒有什麼變化。

排除 其他干擾因素,保守估計 Prepare 批量插入時效能會提升在 20% - 40% 之間,這個變化還是比較明顯的。

三:總結

  1. Prepere 的使用場景是,SQL 語句 未發生改變,只是 query 的值發生了改變的情況。尤其是高併發 批量 SQL 的場景。
  2. Prepere 在批量 插入時 效能提升在 20% - 40%。 但是 select / update 有待測試,感興趣的同學可以測一下。
  3. 當 併發 達到 例項效能上限時,Prepare SQL 和 普通 SQL 的 效能沒有明顯變化。
  4. MySQL 5.6 版本開始支援 Prepere 預處理

參考文件:
https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html

原創系列,轉載請註明出處,謝謝!

相關文章