1.下載並導入數據庫驅動包(注意配置環境變量、數據庫、go、以及ENV)
這里選擇了Go-MySQL-Driver這個實現。地址是:https://github.com/go-sql-driver/mysql/。
然后按照里面的說明下載驅動包:
$ go get github.com/go-sql-driver/mysql
最后導入包即可:
import "database/sql" import _ "github.com/go-sql-driver/mysql"
import
我們在寫Go代碼的時候經常用到import這個命令用來導入包文件,而我們經常看到的方式參考如下:
import(
"fmt"
)
然后我們代碼里面可以通過如下的方式調用
fmt.Println("hello world")
上面這個fmt是Go語言的標准庫,其實是去goroot下去加載該模塊,當然Go的import還支持如下兩種方式來加載自己
寫的模塊:
1. 相對路徑
import “./model” //當前文件同一目錄的model目錄,但是不建議這種方式來import
2. 絕對路徑
import “shorturl/model” //加載gopath/src/shorturl/model模塊
上面展示了一些import常用的幾種方式,但是還有一些特殊的import,讓很多新手很費解,下面我們來一一講解一下
到底是怎么一回事
1. 點操作
我們有時候會看到如下的方式導入包
import(
. "fmt"
)
這個點操作的含義就是這個包導入之后在你調用這個包的函數時,你可以省略前綴的包名,也就是前面你調
用的fmt.Println("hello world")可以省略的寫成Println("hello world")
2. 別名操作
別名操作顧名思義我們可以把包命名成另一個我們用起來容易記憶的名字
import(
f "fmt"
)
別名操作的話調用包函數時前綴變成了我們的前綴,即f.Println("hello world")
3. _操作
這個操作經常是讓很多人費解的一個操作符,請看下面這個import
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
_操作其實是引入該包,而不直接使用包里面的函數,而是調用了該包里面的init函數。
2.連接至數據庫
db, err := sql.Open("mysql", "root:root@/uestcbook")
(1)sql.Open("mysql", "username:pwd@/databasename")
功能:返回一個DB對象,DB對象對於多個goroutines並發使用是安全的,DB對象內部封裝了連接池。
實現:open函數並沒有創建連接,它只是驗證參數是否合法。然后開啟一個單獨goroutines去監聽是否需要建立新的連接,當有請求建立新連接時就創建新連接。
注意:open函數應該被調用一次,通常是沒必要close的。
(2)DB.Exec()
功能:執行不返回行(row)的查詢,比如INSERT,UPDATE,DELETE
實現:DB交給內部的exec方法負責查詢。exec會首先調用DB內部的conn方法從連接池里面獲得一個連接。然后檢查內部的driver.Conn實現了Execer接口沒有,如果實現了該接口,會調用Execer接口的Exec方法執行查詢;否則調用Conn接口的Prepare方法負責查詢。
(3)DB.Query()
功能:用於檢索(retrieval),比如SELECT
實現:DB交給內部的query方法負責查詢。query首先調用DB內部的conn方法從連接池里面獲得一個連接,然后調用內部的queryConn方法負責查詢。
(4)DB.QueryRow()
功能:用於返回單行的查詢
實現:轉交給DB.Query()查詢
(5)db.Prepare()
功能:返回一個Stmt。Stmt對象可以執行Exec,Query,QueryRow等操作。
實現:DB交給內部的prepare方法負責查詢。prepare首先調用DB內部的conn方法從連接池里面獲得一個連接,然后調用driverConn的prepareLocked方法負責查詢。
Stmt相關方法:
st.Exec()
st.Query()
st.QueryRow()
st.Close()
(6)db.Begin()
功能:開啟事務,返回Tx對象。調用該方法后,這個TX就和指定的連接綁定在一起了。一旦事務提交或者回滾,該事務綁定的連接就還給DB的連接池。
實現:DB交給內部的begin方法負責處理。begin首先調用DB內部的conn方法從連接池里面獲得一個連接,然后調用Conn接口的Begin方法獲得一個TX。
TX相關方法:
//內部執行流程和上面那些差不多,只是沒有先去獲取連接的一步,因為這些操作是和TX關聯的,Tx建立的時候就和一個連接綁定了,所以這些操作內部共用一個TX內部的連接。
tx.Exec()
tx.Query()
tx.QueryRow()
tx.Prepare()
tx.Commit()
tx.Rollback()
tx.Stmt()//用於將一個已存在的statement和tx綁定在一起。一個statement可以不和tx關聯,比如db.Prepare()返回的statement就沒有和TX關聯。
幾個主要struct的內部主要的數據結構
(1)Exec
result, err := db.Exec( "INSERT INTO users (name, age) VALUES (?, ?)", "gopher", 27, )
(2)Query
rows, err := db.Query("SELECT name FROM users WHERE age = ?", age) if err != nil { log.Fatal(err) } for rows.Next() { var name string if err := rows.Scan(&name); err != nil { log.Fatal(err) } fmt.Printf("%s is %d\n", name, age) } if err := rows.Err(); err != nil { log.Fatal(err) }
(3)QueryRow
var age int64 row := db.QueryRow("SELECT age FROM users WHERE name = ?", name) err := row.Scan(&age)
(4)Prepared statements
age := 27 stmt, err := db.Prepare("SELECT name FROM users WHERE age = ?") if err != nil { log.Fatal(err) } rows, err := stmt.Query(age) // process rows
4. 事務
tx, err := db.Begin() if err != nil { log.Fatal(err) }
5. 各種方式效率分析
問題:db.exec和statement.exec和tx.exec的區別?
實例如下:
package main import ( "strconv" "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" "time" "log" ) var db = &sql.DB{} func init(){ db,_ = sql.Open("mysql", "root:root@/book") } func main() { insert() query() update() query() delete() } func update(){ //方式1 update start := time.Now() for i := 1001;i<=1100;i++{ db.Exec("UPdate user set age=? where uid=? ",i,i) } end := time.Now() fmt.Println("方式1 update total time:",end.Sub(start).Seconds()) //方式2 update start = time.Now() for i := 1101;i<=1200;i++{ stm,_ := db.Prepare("UPdate user set age=? where uid=? ") stm.Exec(i,i) stm.Close() } end = time.Now() fmt.Println("方式2 update total time:",end.Sub(start).Seconds()) //方式3 update start = time.Now() stm,_ := db.Prepare("UPdate user set age=? where uid=?") for i := 1201;i<=1300;i++{ stm.Exec(i,i) } stm.Close() end = time.Now() fmt.Println("方式3 update total time:",end.Sub(start).Seconds()) //方式4 update start = time.Now() tx,_ := db.Begin() for i := 1301;i<=1400;i++{ tx.Exec("UPdate user set age=? where uid=?",i,i) } tx.Commit() end = time.Now() fmt.Println("方式4 update total time:",end.Sub(start).Seconds()) //方式5 update start = time.Now() for i := 1401;i<=1500;i++{ tx,_ := db.Begin() tx.Exec("UPdate user set age=? where uid=?",i,i) tx.Commit() } end = time.Now() fmt.Println("方式5 update total time:",end.Sub(start).Seconds()) } func delete(){ //方式1 delete start := time.Now() for i := 1001;i<=1100;i++{ db.Exec("DELETE FROM USER WHERE uid=?",i) } end := time.Now() fmt.Println("方式1 delete total time:",end.Sub(start).Seconds()) //方式2 delete start = time.Now() for i := 1101;i<=1200;i++{ stm,_ := db.Prepare("DELETE FROM USER WHERE uid=?") stm.Exec(i) stm.Close() } end = time.Now() fmt.Println("方式2 delete total time:",end.Sub(start).Seconds()) //方式3 delete start = time.Now() stm,_ := db.Prepare("DELETE FROM USER WHERE uid=?") for i := 1201;i<=1300;i++{ stm.Exec(i) } stm.Close() end = time.Now() fmt.Println("方式3 delete total time:",end.Sub(start).Seconds()) //方式4 delete start = time.Now() tx,_ := db.Begin() for i := 1301;i<=1400;i++{ tx.Exec("DELETE FROM USER WHERE uid=?",i) } tx.Commit() end = time.Now() fmt.Println("方式4 delete total time:",end.Sub(start).Seconds()) //方式5 delete start = time.Now() for i := 1401;i<=1500;i++{ tx,_ := db.Begin() tx.Exec("DELETE FROM USER WHERE uid=?",i) tx.Commit() } end = time.Now() fmt.Println("方式5 delete total time:",end.Sub(start).Seconds()) } func query(){ //方式1 query start := time.Now() rows,_ := db.Query("SELECT uid,username FROM USER") defer rows.Close() for rows.Next(){ var name string var id int if err := rows.Scan(&id,&name); err != nil { log.Fatal(err) } //fmt.Printf("name:%s ,id:is %d\n", name, id) } end := time.Now() fmt.Println("方式1 query total time:",end.Sub(start).Seconds()) //方式2 query start = time.Now() stm,_ := db.Prepare("SELECT uid,username FROM USER") defer stm.Close() rows,_ = stm.Query() defer rows.Close() for rows.Next(){ var name string var id int if err := rows.Scan(&id,&name); err != nil { log.Fatal(err) } // fmt.Printf("name:%s ,id:is %d\n", name, id) } end = time.Now() fmt.Println("方式2 query total time:",end.Sub(start).Seconds()) //方式3 query start = time.Now() tx,_ := db.Begin() defer tx.Commit() rows,_ = tx.Query("SELECT uid,username FROM USER") defer rows.Close() for rows.Next(){ var name string var id int if err := rows.Scan(&id,&name); err != nil { log.Fatal(err) } //fmt.Printf("name:%s ,id:is %d\n", name, id) } end = time.Now() fmt.Println("方式3 query total time:",end.Sub(start).Seconds()) } func insert() { //方式1 insert //strconv,int轉string:strconv.Itoa(i) start := time.Now() for i := 1001;i<=1100;i++{ //每次循環內部都會去連接池獲取一個新的連接,效率低下 db.Exec("INSERT INTO user(uid,username,age) values(?,?,?)",i,"user"+strconv.Itoa(i),i-1000) } end := time.Now() fmt.Println("方式1 insert total time:",end.Sub(start).Seconds()) //方式2 insert start = time.Now() for i := 1101;i<=1200;i++{ //Prepare函數每次循環內部都會去連接池獲取一個新的連接,效率低下 stm,_ := db.Prepare("INSERT INTO user(uid,username,age) values(?,?,?)") stm.Exec(i,"user"+strconv.Itoa(i),i-1000) stm.Close() } end = time.Now() fmt.Println("方式2 insert total time:",end.Sub(start).Seconds()) //方式3 insert start = time.Now() stm,_ := db.Prepare("INSERT INTO user(uid,username,age) values(?,?,?)") for i := 1201;i<=1300;i++{ //Exec內部並沒有去獲取連接,為什么效率還是低呢? stm.Exec(i,"user"+strconv.Itoa(i),i-1000) } stm.Close() end = time.Now() fmt.Println("方式3 insert total time:",end.Sub(start).Seconds()) //方式4 insert start = time.Now() //Begin函數內部會去獲取連接 tx,_ := db.Begin() for i := 1301;i<=1400;i++{ //每次循環用的都是tx內部的連接,沒有新建連接,效率高 tx.Exec("INSERT INTO user(uid,username,age) values(?,?,?)",i,"user"+strconv.Itoa(i),i-1000) } //最后釋放tx內部的連接 tx.Commit() end = time.Now() fmt.Println("方式4 insert total time:",end.Sub(start).Seconds()) //方式5 insert start = time.Now() for i := 1401;i<=1500;i++{ //Begin函數每次循環內部都會去連接池獲取一個新的連接,效率低下 tx,_ := db.Begin() tx.Exec("INSERT INTO user(uid,username,age) values(?,?,?)",i,"user"+strconv.Itoa(i),i-1000) //Commit執行后連接也釋放了 tx.Commit() } end = time.Now() fmt.Println("方式5 insert total time:",end.Sub(start).Seconds()) }
之前關於golang操作數據庫的博客:
Go實戰–go語言操作sqlite數據庫(The way to go)
Go實戰–golang中使用redis(redigo和go-redis/redis)
導入包
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
連接數據庫
db, err := sql.Open("mysql", "root:wangshubo@/test?charset=utf8")
checkErr(err)
插入數據
stmt, err := db.Prepare("INSERT user_info SET id=?,name=?")
checkErr(err)
res, err := stmt.Exec(1, "wangshubo")
checkErr(err)
更新數據
stmt, err = db.Prepare("update user_info set name=? where id=?")
checkErr(err)
res, err = stmt.Exec("astaxieupdate", id)
checkErr(err)
查詢
rows, err := db.Query("SELECT * FROM user_info")
checkErr(err)
刪除
stmt, err = db.Prepare("delete from user_info where id=?")
checkErr(err)
res, err = stmt.Exec(id)
checkErr(err)