使用go語言操作mysql資料庫
1.下載並匯入資料庫驅動包
官方不提供實現,先下載第三方的實現,點選這裡檢視各種各樣的實現版本。
這裡選擇了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"
2.連線至資料庫
db, err := sql.Open("mysql", "root:[email protected]/uestcbook")
3.執行查詢
(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:[email protected]/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()) }
程式輸出結果:
方式1 insert total time: 3.7952171
方式2 insert total time: 4.3162468
方式3 insert total time: 4.3392482
方式4 insert total time: 0.3970227
方式5 insert total time: 7.3894226
方式1 query total time: 0.0070004
方式2 query total time: 0.0100006
方式3 query total time: 0.0100006
方式1 update total time: 7.3394198
方式2 update total time: 7.8464488
方式3 update total time: 6.0053435
方式4 update total time: 0.6630379000000001
方式5 update total time: 4.5402597
方式1 query total time: 0.0070004
方式2 query total time: 0.0060004
方式3 query total time: 0.008000400000000001
方式1 delete total time: 3.8652211000000003
方式2 delete total time: 3.8582207
方式3 delete total time: 3.6972114
方式4 delete total time: 0.43202470000000004
方式5 delete total time: 3.7972172
6. 深入內部分析原因分析
(1)sql.Open("mysql", "username:[email protected]/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關聯。
例子:
updateMoney, err := db.Prepare("UPDATE balance SET money=money+? WHERE id=?") ... tx, err := db.Begin() ... res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)
(7)原始碼中Stmt的定義
// Stmt is a prepared statement. Stmt is safe for concurrent use by multiple goroutines. type Stmt struct { // Immutable: db *DB // where we came from query string // that created the Stmt stickyErr error // if non-nil, this error is returned for all operations closemu sync.RWMutex // held exclusively during close, for read otherwise. // If in a transaction, else both nil: tx *Tx txsi *driverStmt mu sync.Mutex // protects the rest of the fields closed bool // css is a list of underlying driver statement interfaces // that are valid on particular connections. This is only // used if tx == nil and one is found that has idle // connections. If tx != nil, txsi is always used. css []connStmt }
(7)幾個主要struct的內部主要的資料結構
參考資料
https://github.com/golang/go/wiki/SQLInterface