1. 程式人生 > >Go語言中database/sql包操作MySQL(檢索結果集)

Go語言中database/sql包操作MySQL(檢索結果集)

有幾種習慣操作可以從資料儲存中檢索結果。

  1. 執行一個返回行的查詢。
  2. 準備重複使用的宣告,多次執行並銷燬它。
  3. 以一次性方式執行陳述,而不準備重複使用。
  4. 執行一個返回單行的查詢。這個特例有一個捷徑。

Go的database/sql函式名稱很重要。如果一個函式名包含Query,它被設計為詢問資料庫的問題,並且將返回一組行,即使它是空的。不返回行的語句不應該使用Query函式; 應該使用Exec()

從資料庫獲取資料

我們來看一個如何查詢資料庫和使用結果的例子。我們將查詢users表格中是否id為1 的使用者,並打印出使用者的idname我們將把結果分配給一次一行的變數rows.Scan()

var(
	id int
name 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)}

以上程式碼中發生了什麼:

  1. 我們正在使用
    db.Query()將查詢傳送到資料庫。像往常一樣檢查錯誤。
  2. 我們推遲rows.Close()這個非常重要。
  3. 我們通過迭代遍歷行rows.Next()
  4. 我們將每行中的列讀入變數中rows.Scan()
  5. 我們在遍歷行之後檢查錯誤。

這幾乎是在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)