Go語言中database/sql包操作MySQL(使用預先準備的語句)
準備好的陳述和連線
在資料庫級別,準備好的語句繫結到單個數據庫連線。典型的流程是客戶端向伺服器傳送帶佔位符的SQL語句以供準備,伺服器用語句ID作出響應,然後客戶端通過傳送其ID和引數來執行語句。
然而,在Go中,連線並不直接暴露給database/sql
包的使用者 。你不準備在連線上發表宣告。你準備在一個DB
或一個Tx
。並database/sql
具有一些便利行為,例如自動重試。由於這些原因,準備好的語句和連線之間的基礎關聯(存在於驅動程式級別)對於您的程式碼而言是隱藏的。
以下是它的工作原理:
- 當您準備一份宣告時,它將在池中的一個連線上準備好。
- 該
Stmt
物件記住使用了哪個連線。 - 當你執行時
Stmt
,它會嘗試使用連線。
由於在原始連線繁忙時將根據需要重新編寫語句,因此可能會導致資料庫的高併發使用率(可能使大量連線繁忙)建立大量預準備語句。這可能會導致明顯的語句洩露,正在準備和重新準備的語句比您想象的更頻繁,甚至會遇到伺服器端語句數量限制。
避免準備好的陳述
Go會在封面上為您建立準備好的語句。db.Query(sql, param1, param2)
例如,一個簡單的 工作是準備sql,然後使用引數執行它,最後關閉語句。
但是,有時準備好的宣告不是你想要的。這可能有幾個原因:
- 資料庫不支援預準備語句。例如,在使用MySQL驅動程式時,您可以連線到MemSQL和Sphinx,因為它們支援MySQL有線協議。
- 這些語句沒有足夠的重用來使它們值得,並且以其他方式處理安全問題,所以效能開銷是不希望的。
如果您不想使用預準備語句,則需要使用fmt.Sprint()
或類似的語法來組裝SQL,並將其作為唯一引數傳遞給db.Query()
or db.QueryRow()
。您的驅動程式需要支援明文查詢執行,這是通過Execer
和Queryer
介面 在Go 1.1中新增的,在這裡記錄。
準備交易報表
在a中建立的準備Tx
好的語句僅限於它,因此早期有關重新編制的警告不適用。當你在一個Tx
物件上進行操作時,你的動作直接對映到它的唯一連線。
這也意味著在一個內部建立的準備語句
Tx
不能單獨使用。同樣,在a上建立的準備語句DB
也不能在事務中使用,因為它們將被繫結到不同的連線。要使用在a中的事務外準備的準備好的語句Tx
,您可以使用 Tx.Stmt()
該語句,它將從事務外準備的語句建立新的特定於事務的語句。它通過採用現有的準備好的語句,設定與事務的連線並在每次執行時重新編寫所有語句來完成此操作。這種行為及其實現是不可取的,甚至在database/sql
原始碼中還有一個TODO 來改進它; 我們建議不要使用這個。
在處理交易中的預先準備的報表時,必須謹慎行事。考慮下面的例子:
tx, err := db.Begin()if err !=nil{ log.Fatal(err)}defer tx.Rollback() stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")if err !=nil{ log.Fatal(err)}defer stmt.Close()// danger!for i :=0; i <10; i++{ _, err = stmt.Exec(i)if err !=nil{ log.Fatal(err)}} err = tx.Commit()if err !=nil{ log.Fatal(err)}// stmt.Close() runs here!
在Go 1.4關閉一個已*sql.Tx
釋放的與它關聯的連接回到池中之前,但在已準備好的語句中延遲呼叫Close 以便在發生後執行 ,這可能會導致併發訪問基礎連線,從而導致連線狀態不一致。如果您使用Go 1.4或更早版本,則應確保在事務提交或回滾之前語句始終關閉。此問題已在CR 131650043的 Go 1.4中修復。
引數佔位符語法
預準備語句中佔位符引數的語法是特定於資料庫的。例如,比較MySQL,PostgreSQL和Oracle:
MySQL PostgreSQL Oracle
===== ========== ======
WHERE col = ? WHERE col = $1 WHERE col = :col
VALUES(?, ?, ?) VALUES($1, $2, $3) VALUES(:val1, :val2, :val3)