1. 程式人生 > >Go語言中database/sql包操作MySQL(使用預先準備的語句)

Go語言中database/sql包操作MySQL(使用預先準備的語句)

準備好的陳述和連線

在資料庫級別,準備好的語句繫結到單個數據庫連線。典型的流程是客戶端向伺服器傳送帶佔位符的SQL語句以供準備,伺服器用語句ID作出響應,然後客戶端通過傳送其ID和引數來執行語句。

然而,在Go中,連線並不直接暴露給database/sql的使用者 你不準備在連線上發表宣告。你準備在一個DB或一個Txdatabase/sql具有一些便利行為,例如自動重試。由於這些原因,準備好的語句和連線之間的基礎關聯(存在於驅動程式級別)對於您的程式碼而言是隱藏的。

以下是它的工作原理:

  1. 當您準備一份宣告時,它將在池中的一個連線上準備好。
  2. Stmt物件記住使用了哪個連線。
  3. 當你執行時Stmt,它會嘗試使用連線。
    如果因為關閉或忙於做其他事情而無法使用,則它會從池中獲得另一個連線,
    並將語句與另一個連線上的資料庫重新進行準備。

由於在原始連線繁忙時將根據需要重新編寫語句,因此可能會導致資料庫的高併發使用率(可能使大量連線繁忙)建立大量預準備語句。這可能會導致明顯的語句洩露,正在準備和重新準備的語句比您想象的更頻繁,甚至會遇到伺服器端語句數量限制。

避免準備好的陳述

Go會在封面上為您建立準備好的語句。db.Query(sql, param1, param2)例如,一個簡單的 工作是準備sql,然後使用引數執行它,最後關閉語句。

但是,有時準備好的宣告不是你想要的。這可能有幾個原因:

  1. 資料庫不支援預準備語句。例如,在使用MySQL驅動程式時,您可以連線到MemSQL和Sphinx,因為它們支援MySQL有線協議。
    但他們不支援包含預準備語句的“二進位制”協議,因此它們可能會以混亂的方式失​​敗。
  2. 這些語句沒有足夠的重用來使它們值得,並且以其他方式處理安全問題,所以效能開銷是不希望的。

如果您不想使用預準備語句,則需要使用fmt.Sprint()或類似的語法來組裝SQL,並將其作為唯一引數傳遞給db.Query() or db.QueryRow()您的驅動程式需要支援明文查詢執行,這是通過ExecerQueryer介面 在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)