關于标準庫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","男","[email protected]")
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/#type Result
我們可以直接這個接口裡隻有兩個方法: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 query without returning any rows.
// The args are for any placeholder parameters in the query.
func (db *DB) ExecContext(ctx context.Context, query string, 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) 設定連接配接可以被使用的最長有效時間,如果過期,連接配接将被拒絕
所有的努力都值得期許,每一份夢想都應該灌溉!