一: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 在高並場景下,效能能提升多少呢,官方並未給出答案,下面準備實測一下:
二: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% 之間,這個變化還是比較明顯的。
三:總結
- Prepere 的使用場景是,SQL 語句 未發生改變,只是 query 的值發生了改變的情況。尤其是高併發 批量 SQL 的場景。
- Prepere 在批量 插入時 效能提升在 20% - 40%。 但是 select / update 有待測試,感興趣的同學可以測一下。
- 當 併發 達到 例項效能上限時,Prepare SQL 和 普通 SQL 的 效能沒有明顯變化。
- MySQL 5.6 版本開始支援 Prepere 預處理
參考文件:
https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html