1. 程式人生 > >Go基礎之--操作Mysql(二)

Go基礎之--操作Mysql(二)

有時 ans ror score 事件 tab 自動 還需要 以及

在上一篇文章中主要整理了Golang連接mysql以及一些基本的操作,並進行了大概介紹,這篇文章對增刪查改進行詳細的整理

讀取數據

在上一篇文章中整理查詢數據的時候,使用了Query的方法查詢,其實database/sql還提供了QueryRow方法查詢數據,就像之前說的database/sql連接創建都是惰性的,所以當我們通過Query查詢數據的時候主要分為三個步驟:

  1. 從連接池中請求一個連接
  2. 執行查詢的sql語句
  3. 將數據庫連接的所屬權傳遞給Result結果集

Query返回的結果集是sql.Rows類型。它有一個Next方法,可以叠代數據庫的遊標,進而獲取每一行的數據,使用方法如下:

//執行查詢操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")
if err != nil{
    fmt.Println("select db failed,err:",err)
    return
}
// 這裏獲取的rows是從數據庫查的滿足user_id>=5的所有行的email信息,rows.Next(),用於循環獲取所有
for rows.Next(){
    var s string
    err = rows.Scan(&s)
    
if err != nil{ fmt.Println(err) return } fmt.Println(s) } rows.Close()

其實當我們通過for循環叠代數據庫的時候,當叠代到最後一樣數據的時候,會出發一個io.EOF的信號,引發一個錯誤,同時go會自動調用rows.Close方法釋放連接,然後返回false,此時循環將會結束退出。

通常你會正常叠代完數據然後退出循環。可是如果並沒有正常的循環而因其他錯誤導致退出了循環。此時rows.Next處理結果集的過程並沒有完成,歸屬於rows的連接不會被釋放回到連接池。因此十分有必要正確的處理rows.Close事件。如果沒有關閉rows連接,將導致大量的連接並且不會被其他函數重用,就像溢出了一樣。最終將導致數據庫無法使用。

所以為了避免這種情況的發生,最好的辦法就是顯示的調用rows.Close方法,確保連接釋放,又或者使用defer指令在函數退出的時候釋放連接,即使連接已經釋放了,rows.Close仍然可以調用多次,是無害的。

rows.Next循環叠代的時候,因為觸發了io.EOF而退出循環。為了檢查是否是叠代正常退出還是異常退出,需要檢查rows.Err。例如上面的代碼應該改成:

//Query執行查詢操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")
if err != nil{
    fmt.Println("select db failed,err:",err)
    return
}
// 這裏獲取的rows是從數據庫查的滿足user_id>=5的所有行的email信息,rows.Next(),用於循環獲取所有
for rows.Next(){
    var s string
    err = rows.Scan(&s)
    if err != nil{
        fmt.Println(err)
        return
    }
    fmt.Println(s)
}
rows.Close()
if err = rows.Err();err != nil{
    fmt.Println(err)
    return 
}

讀取單條數據

Query方法是讀取多行結果集,實際開發中,很多查詢只需要單條記錄,不需要再通過Next叠代。golang提供了QueryRow方法用於查詢單條記錄的結果集。

QueryRow方法的使用很簡單,它要麽返回sql.Row類型,要麽返回一個error,如果是發送了錯誤,則會延遲到Scan調用結束後返回,如果沒有錯誤,則Scan正常執行。只有當查詢的結果為空的時候,會觸發一個sql.ErrNoRows錯誤。你可以選擇先檢查錯誤再調用Scan方法,或者先調用Scan再檢查錯誤。

在之前的代碼中我們都用到了Scan方法,下面說說關於這個方法

結果集方法Scan可以把數據庫取出的字段值賦值給指定的數據結構。它的參數是一個空接口的切片,這就意味著可以傳入任何值。通常把需要賦值的目標變量的指針當成參數傳入,它能將數據庫取出的值賦值到指針值對象上。
代碼例子如:

// 查詢數據
var username string
var email string
rows  := Db.QueryRow("SELECT username,email FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email)
if err != nil{
    fmt.Println("scan err:",err)
    return
}
fmt.Println(username,email)

Scan還會幫我們自動推斷除數據字段匹配目標變量。比如有個數據庫字段的類型是VARCHAR,而他的值是一個數字串,例如"1"。如果我們定義目標變量是string,則scan賦值後目標變量是數字string。如果聲明的目標變量是一個數字類型,那麽scan會自動調用strconv.ParseInt()或者strconv.ParseInt()方法將字段轉換成和聲明的目標變量一致的類型。當然如果有些字段無法轉換成功,則會返回錯誤。因此在調用scan後都需要檢查錯誤。

空值處理

數據庫有一個特殊的類型,NULL空值。可是NULL不能通過scan直接跟普遍變量賦值,甚至也不能將null賦值給nil。對於null必須指定特殊的類型,這些類型定義在database/sql庫中。例如sql.NullFloat64,sql.NullString,sql.NullBool,sql.NullInt64。如果在標準庫中找不到匹配的類型,可以嘗試在驅動中尋找。下面是一個簡單的例子:

下面代碼,數據庫中create_time為Null這個時候,如果直接這樣查詢,會提示錯誤:

// 查詢數據
var username string
var email string
var createTime string
rows  := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime)
if err != nil{
    fmt.Println("scan err:",err)
    return
}
fmt.Println(username,email,createTime)

錯誤內容如下:

scan err: sql: Scan error on column index 2: unsupported Scan, storing driver.Value type <nil> into type *string

所以需要將代碼更改為:

// 查詢數據
var username string
var email string
var createTime sql.NullString
rows  := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime)
if err != nil{
    fmt.Println("scan err:",err)
    return
}
fmt.Println(username,email,createTime)

執行結果為:

user01 [email protected] { false}

我將數據庫中添加了一列,是int類型,同樣的默認值是Null,代碼為:

// 查詢數據
var username string
var email string
var createTime string
var score int
rows  := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
rows.Scan(&username,&email,&createTime,&score)
fmt.Println(username,email,createTime,score)

其實但我們忽略錯誤直接輸出的時候,也可以輸出,當然Null的字段都被轉換為了零值
而當我們按照上面的方式處理後,代碼為:

// 查詢數據
var username string
var email string
var createTime sql.NullString
var score sql.NullInt64
rows  := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime,&score)
if err != nil{
    fmt.Println("scan fail,err:",err)
    return
}
fmt.Println(username,email,createTime,score)

輸出的結果為:

user01 [email protected] { false} {0 false}

對Null的操作,一般還是需要驗證的,代碼如下:

// 查詢數據
var score sql.NullInt64
rows  := Db.QueryRow("SELECT socre FROM user_info WHERE user_id=6")
err = rows.Scan(&score)
if err != nil{
    fmt.Println("scan fail,err:",err)
    return
}
if score.Valid{
    fmt.Println("res:",score.Int64)
}else{
    fmt.Println("err",score.Int64)
}

這裏我已經在數據庫給字段添加內容了,所以這裏默認輸出10,但是當還是Null的時候輸出的則是零值
但是有時候我們如果不關心是不是Null的時候,只是想把它當做空字符串處理就行,我們也可以使用[]byte,代碼如下:

// 查詢數據
var score []byte
var modifyTime []byte
rows  := Db.QueryRow("SELECT modify_time,socre FROM user_info WHERE user_id=6")
err = rows.Scan(&modifyTime,&score)
if err != nil{
    fmt.Println("scan fail,err:",err)
    return
}
fmt.Println(string(modifyTime),string(score))

這樣處理後,如果有值則可以獲取值,如果沒有則獲取的為空字符串

自動匹配字段

上面查詢的例子中,我們都自己定義了變量,同時查詢的時候也寫明了字段,如果不指名字段,或者字段的順序和查詢的不一樣,都有可能出錯。因此如果能夠自動匹配查詢的字段值,將會十分節省代碼,同時也易於維護。
go提供了Columns方法用獲取字段名,與大多數函數一樣,讀取失敗將會返回一個err,因此需要檢查錯誤。
代碼例子如下:

// 查詢數據

rows,err:= Db.Query("SELECT * FROM user_info WHERE user_id>6")
if err != nil{
    fmt.Println("select fail,err:",err)
    return
}
cols,err := rows.Columns()
if err != nil{
    fmt.Println("get columns fail,err:",err)
    return
}
fmt.Println(cols)
vals := make([][]byte, len(cols))
scans := make([]interface{},len(cols))


for i := range vals{
    scans[i] = &vals[i]
}
fmt.Println(scans)
var results []map[string]string

for rows.Next(){
    err = rows.Scan(scans...)
    if err != nil{
        fmt.Println("scan fail,err:",err)
        return
    }
    row := make(map[string]string)
    for k,v:=range vals{
        key := cols[k]
        row[key] =string(v)
    }
    results = append(results,row)
}

for k,v:=range results{
    fmt.Println(k,v)
}

因為查詢的時候是語句是:
SELECT * FROM user_info WHERE user_id>6
這樣就會獲取每行數據的所有的字段
使用rows.Columns()獲取字段名,是一個string的數組
然後創建一個切片vals,用來存放所取出來的數據結果,類似是byte的切片。接下來還需要定義一個切片,這個切片用來scan,將數據庫的值復制到給它
vals則得到了scan復制給他的值,因為是byte的切片,因此在循環一次,將其轉換成string即可。
轉換後的row即我們取出的數據行值,最後組裝到result切片中。

上面代碼的執行結果為:

[user_id username sex email create_time modify_time socre]
[0xc4200c6000 0xc4200c6018 0xc4200c6030 0xc4200c6048 0xc4200c6060 0xc4200c6078 0xc4200c6090]
0 map[user_id:7 username:user01 sex:男 email:[email protected] create_time:2018-03-05 14:10:08 modify_time: socre:]
1 map[username:user11 sex:男 email:[email protected] create_time:2018-03-05 14:10:11 modify_time: socre: user_id:8]
2 map[sex:男 email:[email protected] create_time:2018-03-05 14:10:15 modify_time: socre: user_id:9 username:user12]

通過上面例子的整理以及上面文章的整理,我們基本可以知道:
Exec的時候通常用於執行插入和更新操作
Query以及QueryRow通常用於執行查詢操作

Exec執行完畢之後,連接會立即釋放回到連接池中,因此不需要像query那樣再手動調用row的close方法。

Go基礎之--操作Mysql(二)