這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
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:root@/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 int64row := db.QueryRow("SELECT age FROM users WHERE name = ?", name)err := row.Scan(&age)
(4)Prepared statements
age := 27stmt, 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 mainimport ( "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())}
程式輸出結果:
方式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: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關聯。
例子:
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的內部主要的資料結構
361 次點擊