golang自己沒有提供鏈接mysql的驅動,可是定義了標準接口供第三方開發驅動。這裏鏈接mysql可使用第三方庫,第三方庫推薦使用https://github.com/Go-SQL-Driver/MySQL這個驅動,更新維護都比較好。下面演示下具體的使用,完整代碼示例能夠參考最後。mysql
sudo go get github.com/go-sql-driver/mysql
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?charset=utf8")
其中鏈接參數能夠有以下幾種形式:一般咱們都用第二種。git
user@unix(/path/to/socket)/dbname?charset=utf8 user:password@tcp(localhost:5555)/dbname?charset=utf8 user:password@/dbname user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname
stmt, err := db.Prepare(`INSERT user (user_name,user_age,user_sex) values (?,?,?)`) checkErr(err) res, err := stmt.Exec("tony", 20, 1) checkErr(err) id, err := res.LastInsertId() checkErr(err) fmt.Println(id)
這裏使用結構化操做,不推薦使用直接拼接sql語句的方法。github
rows, err := db.Query("SELECT * FROM user") checkErr(err) for rows.Next() { var userId int var userName string var userAge int var userSex int rows.Columns() err = rows.Scan(&userId, &userName, &userAge, &userSex) checkErr(err) fmt.Println(userId) fmt.Println(userName) fmt.Println(userAge) fmt.Println(userSex) }
這裏查詢的方式使用聲明4個獨立變量userId、userName、userAge、userSex來保存查詢出來的每一行的值。在實際開發中一般會封裝數據庫的操做,對這樣的查詢一般會考慮返回字典類型。golang
//構造scanArgs、values兩個數組,scanArgs的每一個值指向values相應值的地址 columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for i := range values { scanArgs[i] = &values[i] } for rows.Next() { //將行數據保存到record字典 err = rows.Scan(scanArgs...) record := make(map[string]string) for i, col := range values { if col != nil { record[columns[i]] = string(col.([]byte)) } } fmt.Println(record) }
stmt, err := db.Prepare(`UPDATE user SET user_age=?,user_sex=? WHERE user_id=?`) checkErr(err) res, err := stmt.Exec(21, 2, 1) checkErr(err) num, err := res.RowsAffected() checkErr(err) fmt.Println(num)
stmt, err := db.Prepare(`DELETE FROM user WHERE user_id=?`) checkErr(err) res, err := stmt.Exec(1) checkErr(err) num, err := res.RowsAffected() checkErr(err) fmt.Println(num)
修改和刪除操做都比較簡單,同插入數據相似,只是使用RowsAffected來獲取影響的數據行數。web
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { insert() } //插入demo func insert() { db, err := sql.Open("mysql", "root:@/test?charset=utf8") checkErr(err) stmt, err := db.Prepare(`INSERT user (user_name,user_age,user_sex) values (?,?,?)`) checkErr(err) res, err := stmt.Exec("tony", 20, 1) checkErr(err) id, err := res.LastInsertId() checkErr(err) fmt.Println(id) } //查詢demo func query() { db, err := sql.Open("mysql", "root:@/test?charset=utf8") checkErr(err) rows, err := db.Query("SELECT * FROM user") checkErr(err) //普通demo //for rows.Next() { // var userId int // var userName string // var userAge int // var userSex int // rows.Columns() // err = rows.Scan(&userId, &userName, &userAge, &userSex) // checkErr(err) // fmt.Println(userId) // fmt.Println(userName) // fmt.Println(userAge) // fmt.Println(userSex) //} //字典類型 //構造scanArgs、values兩個數組,scanArgs的每一個值指向values相應值的地址 columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for i := range values { scanArgs[i] = &values[i] } for rows.Next() { //將行數據保存到record字典 err = rows.Scan(scanArgs...) record := make(map[string]string) for i, col := range values { if col != nil { record[columns[i]] = string(col.([]byte)) } } fmt.Println(record) } } //更新數據 func update() { db, err := sql.Open("mysql", "root:@/test?charset=utf8") checkErr(err) stmt, err := db.Prepare(`UPDATE user SET user_age=?,user_sex=? WHERE user_id=?`) checkErr(err) res, err := stmt.Exec(21, 2, 1) checkErr(err) num, err := res.RowsAffected() checkErr(err) fmt.Println(num) } //刪除數據 func remove() { db, err := sql.Open("mysql", "root:@/test?charset=utf8") checkErr(err) stmt, err := db.Prepare(`DELETE FROM user WHERE user_id=?`) checkErr(err) res, err := stmt.Exec(1) checkErr(err) num, err := res.RowsAffected() checkErr(err) fmt.Println(num) } func checkErr(err error) { if err != nil { panic(err) } }
golang內部自帶了鏈接池功能,剛開始接觸golang的時候不瞭解這個,還本身搞了一個 sql.Open的對象管理池,真的很是囧啊。sql
sql.Open函數其實是返回一個鏈接池對象,不是單個鏈接。在open的時候並無去鏈接數據庫,只有在執行query、exce方法的時候纔會去實際鏈接數據庫。在一個應用中一樣的庫鏈接只須要保存一個sql.Open以後的db對象就能夠了,不須要屢次open。數據庫
由於普通程序執行完畢以後資源就會被釋放掉,因此這裏嘗試使用web服務進行演示。數組
首頁先啓動一個web服務監聽9090端口,比較簡單很少作說明。併發
func startHttpServer() { http.HandleFunc("/pool", pool) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } }
聲明一個全局的db對象,並進行初始化。socket
var db *sql.DB func init() { db, _ = sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8") db.SetMaxOpenConns(2000) db.SetMaxIdleConns(1000) db.Ping() }
鏈接池的實現關鍵在於SetMaxOpenConns和SetMaxIdleConns,其中:
SetMaxOpenConns用於設置最大打開的鏈接數,默認值爲0表示不限制。
SetMaxIdleConns用於設置閒置的鏈接數。
設置最大的鏈接數,能夠避免併發過高致使鏈接mysql出現too many connections的錯誤。設置閒置的鏈接數則當開啓的一個鏈接使用完成後能夠放在池裏等候下一次使用。
上面開啓http請求設置了請求/pool地址的執行方法
func pool(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT * FROM user limit 1") defer rows.Close() checkErr(err) columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for j := range values { scanArgs[j] = &values[j] } record := make(map[string]string) for rows.Next() { //將行數據保存到record字典 err = rows.Scan(scanArgs...) for i, col := range values { if col != nil { record[columns[i]] = string(col.([]byte)) } } } fmt.Println(record) fmt.Fprintln(w, "finish") } func checkErr(err error) { if err != nil { fmt.Println(err) panic(err) } }
pool方法就是從user表中查出一條記錄而後存放到map中,最後輸出finish。代碼到這裏就算完了很是簡單,下面來測試一下。首先啓動http服務,而後使用ab進行併發測試訪問:
ab -c 100 -n 1000 'http://localhost:9090/pool'
在數據庫中經過show processlist查看鏈接進程:
golang數據庫鏈接池
能夠看到有100來個進程。
由於避免了重複建立鏈接,因此使用鏈接池能夠很明顯的提升性能。有興趣的童靴能夠去掉鏈接池代碼本身測試一下。完整代碼以下:
//數據庫鏈接池測試 package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log" "net/http" ) var db *sql.DB func init() { db, _ = sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8") db.SetMaxOpenConns(2000) db.SetMaxIdleConns(1000) db.Ping() } func main() { startHttpServer() } func startHttpServer() { http.HandleFunc("/pool", pool) err := http.ListenAndServe(":9090", nil) if err != nil { log.Fatal("ListenAndServe: ", err) } } func pool(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT * FROM user limit 1") defer rows.Close() checkErr(err) columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for j := range values { scanArgs[j] = &values[j] } record := make(map[string]string) for rows.Next() { //將行數據保存到record字典 err = rows.Scan(scanArgs...) for i, col := range values { if col != nil { record[columns[i]] = string(col.([]byte)) } } } fmt.Println(record) fmt.Fprintln(w, "finish") } func checkErr(err error) { if err != nil { fmt.Println(err) panic(err) } }
golang這邊實現的鏈接池只提供了SetMaxOpenConns和SetMaxIdleConns方法進行鏈接池方面的配置。在使用的過程當中有一個問題就是數據庫自己對鏈接有一個超時時間的設置,若是超時時間到了數據庫會單方面斷掉鏈接,此時再用鏈接池內的鏈接進行訪問就會出錯。
packets.go:32: unexpected EOF packets.go:118: write tcp 192.168.3.90:3306: broken pipe
上面都是錯誤都是go-sql-drive自己的輸出,有的時候還會出現bad connection的錯誤。多請求幾回後鏈接池會從新打開新鏈接這時候就沒有問題了。