Go語言中database/sql包操作MySQL(檢索結果集)
有幾種習慣操作可以從資料儲存中檢索結果。
- 執行一個返回行的查詢。
- 準備重複使用的宣告,多次執行並銷燬它。
- 以一次性方式執行陳述,而不準備重複使用。
- 執行一個返回單行的查詢。這個特例有一個捷徑。
Go的database/sql
函式名稱很重要。如果一個函式名包含Query
,它被設計為詢問資料庫的問題,並且將返回一組行,即使它是空的。不返回行的語句不應該使用Query
函式; 應該使用Exec()
。
從資料庫獲取資料
我們來看一個如何查詢資料庫和使用結果的例子。我們將查詢users
表格中是否id
為1 的使用者,並打印出使用者的id
和name
。我們將把結果分配給一次一行的變數rows.Scan()
。
var( id intname string) rows, err := db.Query("select id, name from users where id = ?",1)if err !=nil{ log.Fatal(err)}defer rows.Close()for rows.Next(){ err := rows.Scan(&id,&name)if err !=nil{ log.Fatal(err)} log.Println(id, name)} err = rows.Err()if err !=nil{ log.Fatal(err)}
以上程式碼中發生了什麼:
- 我們正在使用
db.Query()
將查詢傳送到資料庫。像往常一樣檢查錯誤。 - 我們推遲
rows.Close()
。這個非常重要。 - 我們通過迭代遍歷行
rows.Next()
。 - 我們將每行中的列讀入變數中
rows.Scan()
。 - 我們在遍歷行之後檢查錯誤。
這幾乎是在Go中完成的唯一方法。例如,您不能將某行作為地圖。那是因為一切都是強型別的。您需要建立正確型別的變數並將指標傳遞給它們,如圖所示。
這很容易出錯,並可能造成不良後果。
- 您應該始終在
for rows.Next()
迴圈結束時檢查錯誤。如果在迴圈過程中出現錯誤,您需要了解它。不要只假定迴圈迭代直到你處理了所有的行。 - 其次,只要有一個開放的結果集(由表示
rows
),底層連線就是繁忙,不能用於任何其他查詢。rows.Next()
,最終您將讀取最後一行,並且rows.Next()
會遇到內部EOF錯誤並請求您rows.Close()。但是,如果由於某種原因您退出該迴圈 - 提前退貨等等 - 則rows
不會關閉,並且連線保持開啟狀態。(如果rows.Next()
由於錯誤返回錯誤,它會自動關閉)。這是耗盡資源的簡單方法。 rows.Close()
如果它已經關閉,它是一個無害的無操作,所以你可以多次呼叫它。但是,請注意,我們首先檢查錯誤,並且只有rows.Close()
在沒有錯誤時才會呼叫,以避免執行時painc。- 即使您 在迴圈結束時也明確地呼叫,但您應該始終
defer rows.Close()
這樣做rows.Close()
,這不是一個壞主意。 - defer不要在一個迴圈內。直到函式退出才會執行延遲語句,因此長時間執行的函式不應使用它。如果你這樣做,你會慢慢積累記憶。如果您在迴圈中反覆查詢和使用結果集,則應
rows.Close()
在完成每個結果時明確呼叫,而不要使用defer
。
Scan()如何工作
當您遍歷行並將它們掃描到目標變數時,Go會在幕後為您執行資料型別轉換。它基於目標變數的型別。意識到這可以清理你的程式碼並幫助避免重複的工作。
例如,假設您從用字串列(如VARCHAR(45)
或類似的)定義的表中選擇一些行。然而,你碰巧知道表格總是包含數字。如果你傳遞一個指向字串的指標,Go會將位元組複製到字串中。現在您可以使用strconv.ParseInt()
或類似的將該值轉換為數字。您必須檢查SQL操作中的錯誤以及解析整數的錯誤。這是混亂和乏味。
或者,您可以將Scan()
一個指標傳遞給一個整數。Go會檢測到並呼喚strconv.ParseInt()
你。如果轉換中出現錯誤,則呼叫Scan()
將返回它。你的程式碼現在更整潔,更小。這是推薦database/sql的使用方式。
準備查詢
總體而言,您應該始終準備要多次使用的查詢。準備查詢的結果是一個準備好的語句,它可以為您在執行語句時提供的引數設定佔位符(又名繫結值)。對於所有常見原因(例如,避免SQL注入攻擊),這比串聯字串要好得多。
在MySQL中,引數佔位符是?
,而在PostgreSQL中它是$N
,其中N是一個數字。SQLite接受這些。在Oracle佔位符以冒號開頭並被命名,就像:param1
。我們將使用,?
因為我們使用MySQL作為示例。
stmt, err := db.Prepare("select id, name from users where id = ?")if err !=nil{ log.Fatal(err)}defer stmt.Close() rows, err := stmt.Query(1)if err !=nil{ log.Fatal(err)}defer rows.Close()for rows.Next(){// ...}if err = rows.Err(); err !=nil{ log.Fatal(err)}
引擎下,db.Query()
居然準備,執行和關閉一個準備好的宣告。這是對資料庫的三次往返。如果你不小心,你可以將你的應用程式所做的資料庫互動次數增加三倍!一些司機可以在特定情況下避免這種情況,但並非所有司機都這樣做。
單行查詢
如果查詢最多隻返回一行,則可以在一些冗長的樣板程式碼上使用快捷方式:
var name string err = db.QueryRow("select name from users where id = ?",1).Scan(&name)if err !=nil{ log.Fatal(err)} fmt.Println(name)
查詢中的錯誤被推遲直到Scan()
被呼叫,然後從那裡返回。您也可以讓QueryRow()
準備好的宣告:
stmt, err := db.Prepare("select name from users where id = ?")if err !=nil{ log.Fatal(err)}var name string err = stmt.QueryRow(1).Scan(&name)if err !=nil{ log.Fatal(err)} fmt.Println(name)