關于標準庫database/sql
database/sql是golang的標準庫之一,它提供了一系列接口方法,用于訪問關系數據庫。它并不會提供數據庫特有的方法,那些特有的方法交給數據庫驅動去實現。
database/sql庫提供了一些type。這些類型對掌握它的用法非常重要。
DB
數據庫對象。 sql.DB類型代表了數據庫。和其他語言不一樣,它并是數據庫連接。golang中的連接來自內部實現的連接池,連接的建立是惰性的,當你需要連接的時候,連接池會自動幫你創建。通常你不需要操作連接池。一切都有go來幫你完成。
Results
結果集。數據庫查詢的時候,都會有結果集。sql.Rows類型表示查詢返回多行數據的結果集。sql.Row則表示單行查詢結果的結果集。當然,對于插入更新和刪除,返回的結果集類型為sql.Result。
Statements
語句。sql.Stmt類型表示sql查詢語句,例如DDL,DML等類似的sql語句。可以把當成prepare語句構造查詢,也可以直接使用sql.DB的函數對其操作。
而通常工作中我們可能更多的是用https://github.com/jmoiron/sqlx包來操作數據庫
sqlx是基于標準庫database/sql的擴展,并且我們可以通過sqlx操作各種類型的數據如
和其他語言不通的是,查詢數據庫的時候需要創建一個連接,對于go而言則是需要創建一個數據庫對象,連接將會在查詢需要的時候,由連接池創建并維護,使用sql.Open函數創建數據庫對象,第一個參數是數據庫驅動名,第二個參數是一個連接字符串
關于數據庫的增刪查改
增加數據
關于增加數據幾個小知識點:
關于插入數據的時候占位符是通過問號:?
插入數據的后可以通過LastInsertId可以獲取插入數據的id
通過RowsAffected可以獲取受影響的行數
執行sql語句是通過exec
一個簡單的使用例子:
package main
import ("github.com/jmoiron/sqlx"_"github.com/go-sql-driver/mysql"
"fmt")
func main() {
Db,err:=sqlx.Open("mysql","root:123456@tcp(192.168.14.7:3306)/godb")if err !=nil{
fmt.Println("connect to mysql failed,",err)
return
}
defer Db.Close()
fmt.Println("connect to mysql success")//執行sql語句,切記這里的占位符是?
result,err := Db.Exec("INSERT INTO user_info(username,sex,email)VALUES (?,?,?)","user01","男","8989@qq.com")if err !=nil{
fmt.Println("insert failed,",err)
}//通過LastInsertId可以獲取插入數據的id
userId,err:=result.LastInsertId()//通過RowsAffected可以獲取受影響的行數
rowCount,err:=result.RowsAffected()
fmt.Println("user_id:",userId)
fmt.Println("rowCount:",rowCount)
}
通過Exec方法插入數據,返回的結果是一個sql.Result類型
查詢數據
下面是一個查詢的例子代碼:
//執行查詢操作
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()
使用了Query方法執行select查詢語句,返回的是一個sql.Rows類型的結果集
迭代后者的Next方法,然后使用Scan方法給變量s賦值,以便取出結果。最后再把結果集關閉(釋放連接)。
同樣的我們還可以通過Exec方式執行查詢語句
但是因為Exec返回的是一個sql.Result類型,從官網這里:
https://golang.google.cn/pkg/database/sql/#typeResult
我們可以直接這個接口里只有兩個方法:LastInsertId(),RowsAffected()
我們還可以通過Db.Get()方法獲取查詢的數據,將查詢的數據保存到一個結構體中
//Get執行查詢操作
type user_info struct {
Username string `db:"username"`
Email string `db:"email"`
}
var userInfo user_info
err= Db.Get(&userInfo,"SELECT username,email FROM user_info WHERE user_id=5")if err !=nil{
fmt.Println(err)
return
}
fmt.Println(userInfo)
這樣獲取的一個數據,如果我們需要獲取多行數據信息還可以通過Db.Select方法獲取數據,代碼例子為:
var userList []*user_info
err= Db.Select(&userList,"SELECT username,email FROM user_info WHERE user_id>5")if err !=nil{
fmt.Println(err)
return
}
fmt.Println(userList)
for _,v:=range userList{
fmt.Println(v)
}
通過Db.Select方法將查詢的多行數據保存在一個切片中,然后就可以通過循環的方式獲取每行數據
更新數據
下面是一個更新的例子,這里是通過Exec的方式執行的
//更新數據
results,err := Db.Exec("UPDATE user_info SET username=? where user_id=?","golang",5)if err !=nil{
fmt.Println("update data fail,err:",err)
return
}
fmt.Println(results.RowsAffected())
刪除數據
下面是一個刪除的例子,同樣是通過Exec的方式執行的
//刪除數據
results,err := Db.Exec("DELETE from user_info where user_id=?",5)if err !=nil{
fmt.Println("delete data fail,err:",err)
return
}
fmt.Println(results.RowsAffected())
通過上面的簡單例子,對golang操作mysql的增刪查改,有了一個基本的了解,下面整理一下重點內容
sql.DB
當我們調用sqlx.Open()可以獲取一個sql.DB對象,sql.DB是數據庫的抽象,切記它不是數據庫連接,sqlx.Open()只是驗證數據庫參數,并沒不創建數據庫連接。sql.DB提供了和數據庫交互的函數,同時也管理維護一個數據庫連接池,并且對于多gegoroutines也是安全的
sql.DB表示是數據庫抽象,因此你有幾個數據庫就需要為每一個數據庫創建一個sql.DB對象。因為它維護了一個連接池,因此不需要頻繁的創建和銷毀。
連接池
只用sql.Open函數創建連接池,可是此時只是初始化了連接池,并沒有創建任何連接。連接創建都是惰性的,只有當真正使用到連接的時候,連接池才會創建連接。連接池很重要,它直接影響著你的程序行為。
連接池的工作原來卻相當簡單。當你的函數(例如Exec,Query)調用需要訪問底層數據庫的時候,函數首先會向連接池請求一個連接。如果連接池有空閑的連接,則返回給函數。否則連接池將會創建一個新的連接給函數。一旦連接給了函數,連接則歸屬于函數。函數執行完畢后,要不把連接所屬權歸還給連接池,要么傳遞給下一個需要連接的(Rows)對象,最后使用完連接的對象也會把連接釋放回到連接池。
請求連接的函數有幾個,執行完畢處理連接的方式也不同:
db.Ping() 調用完畢后會馬上把連接返回給連接池。
db.Exec() 調用完畢后會馬上把連接返回給連接池,但是它返回的Result對象還保留這連接的引用,當后面的代碼需要處理結果集的時候連接將會被重用。
db.Query() 調用完畢后會將連接傳遞給sql.Rows類型,當然后者迭代完畢或者顯示的調用.Clonse()方法后,連接將會被釋放回到連接池。
db.QueryRow()調用完畢后會將連接傳遞給sql.Row類型,當.Scan()方法調用之后把連接釋放回到連接池。
db.Begin() 調用完畢后將連接傳遞給sql.Tx類型對象,當.Commit()或.Rollback()方法調用后釋放連接。
每個連接都是惰性的,如何驗證sql.Open調用之后,sql.DB對象可用,通過db.Ping()初始化
代碼例子:
package main
import ("github.com/jmoiron/sqlx"_"github.com/go-sql-driver/mysql"
"fmt")
func main() {
Db, err := sqlx.Open("mysql", "root:123456@tcp(192.168.50.166:3306)/godb")if err !=nil {
fmt.Println("connect to mysql failed,", err)
return
}
defer Db.Close()
fmt.Println("connect to mysql success")
err=Db.Ping()if err !=nil{
fmt.Println(err)
return
}
fmt.Println("ping success")
}
需要知道:當調用了ping之后,連接池一定會初始化一個數據連接
連接失敗
database/sql 其實幫我們做了很多事情,我們不用見擦汗連接失敗的情況,當我們進行數據庫操作的時候,如果連接失敗,database/sql 會幫我們處理,它會自動連接2次,這個如果查看源碼中我們可以看到如下的代碼:
// ExecContext executes a querywithout returning any rows.// The args are for any placeholder parameters in the query.
func (db*DB) ExecContext(ctx context.Context, querystring, args ...interface{}) (Result, error) {
var res Result
var err error
for i := 0; i < maxBadConnRetries; i++{
res, err= db.exec(ctx, query, args, cachedOrNewConn)if err !=driver.ErrBadConn {
break
}
}if err ==driver.ErrBadConn {
return db.exec(ctx,query, args, alwaysNewConn)
}
return res, err
}
上述代碼中變量maxBadConnRetries小時如果連接失敗嘗試的次數,默認是2
關于連接池配置
db.SetMaxIdleConns(n int) 設置連接池中的保持連接的最大連接數。默認也是0,表示連接池不會保持釋放會連接池中的連接的連接狀態:即當連接釋放回到連接池的時候,連接將會被關閉。這會導致連接再連接池中頻繁的關閉和創建。
db.SetMaxOpenConns(n int) 設置打開數據庫的最大連接數。包含正在使用的連接和連接池的連接。如果你的函數調用需要申請一個連接,并且連接池已經沒有了連接或者連接數達到了最大連接數。此時的函數調用將會被block,直到有可用的連接才會返回。設置這個值可以避免并發太高導致連接mysql出現too many connections的錯誤。該函數的默認設置是0,表示無限制。
db.SetConnMaxLifetime(d time.Duration) 設置連接可以被使用的最長有效時間,如果過期,連接將被拒絕