Go操作mysql实现增删改查及连接池
[-]
golang本身沒有提供連接mysql的驅動,但是定義了標準接口供第三方開發驅動。這里連接mysql可以使用第三方庫,第三方庫推薦使用https://github.com/Go-SQL-Driver/MySQL這個驅動,更新維護都比較好。下面演示下具體的使用,完整代碼示例可以參考最后。
下載驅動
sudo go get github.com/go-sql-driver/mysql如果提示這樣的失敗信息:cannot download, $GOPATH not set. For more details see: go help gopath,可以使用如下命令解決
sudo env GOPATH=/Users/chenjiebin/golang go get github.com/go-sql-driver/mysqlGOPATH的值根據自行環境進行替換。
創建測試表
在mysql test庫中創建測試表
CREATE TABLE IF NOT EXISTS `test`.`user` (`user_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用戶編號',`user_name` VARCHAR(45) NOT NULL COMMENT '用戶名稱',`user_age` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用戶年齡',`user_sex` TINYINT(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用戶性別',PRIMARY KEY (`user_id`))ENGINE = InnoDBAUTO_INCREMENT = 1DEFAULT CHARACTER SET = utf8COLLATE = utf8_general_ciCOMMENT = '用戶表'數據庫連接
數據庫連接使用datebase/sql Open函數進行連接
db, err := sql.Open("mysql", "user:password@tcp(localhost:5555)/dbname?charset=utf8")其中連接參數可以有如下幾種形式:
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語句的方法。
查詢操作
rows, err := db.Query("SELECT * FROM user") checkErr(err)for rows.Next() {var userId intvar userName stringvar userAge intvar userSex introws.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來保存查詢出來的每一行的值。在實際開發中通常會封裝數據庫的操作,對這樣的查詢通常會考慮返回字典類型。
//構造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來獲取影響的數據行數。
完整代碼
package mainimport ("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)} }小結
整體上來說都比較簡單,就是查詢那邊使用字典來存儲返回數據比較復雜一些。既然說到數據庫連接,通常應用中都會使用連接池來減少連接開銷,關于連接池下次整理一下再放上來。
參考資料:《go web編程》,go web編程中對數據庫連接做了比較詳細的解說,值得一看。
轉載請注明:快樂編程???golang連接mysql操作示例增刪改查
?
golang內部自帶了連接池功能,剛開始接觸golang的時候不了解這個,還自己搞了一個 sql.Open的對象管理池,真的非常囧啊。
sql.Open函數實際上是返回一個連接池對象,不是單個連接。在open的時候并沒有去連接數據庫,只有在執行query、exce方法的時候才會去實際連接數據庫。在一個應用中同樣的庫連接只需要保存一個sql.Open之后的db對象就可以了,不需要多次open。
golang中關于mysql的增刪改查我在前面的一篇文章中有說明了,不了解的小伙們可以先去了解一下:golang連接mysql操作示例增刪改查
因為普通程序執行完畢之后資源就會被釋放掉,所以這里嘗試使用web服務進行演示。
開啟web服務
首頁先啟動一個web服務監聽9090端口,比較簡單不多做說明。
func startHttpServer() {http.HandleFunc("/pool", pool)err := http.ListenAndServe(":9090", nil)if err != nil {log.Fatal("ListenAndServe: ", err)} }db對象初始化
聲明一個全局的db對象,并進行初始化。
var db *sql.DBfunc 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 mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql""log""net/http" )var db *sql.DBfunc 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的錯誤。多請求幾次后連接池會重新打開新連接這時候就沒有問題了。關于這個問題自己有初步的解決方法,但是感覺不太完美,下次再放上來。
轉載請注明:快樂編程???golang go-sql-drive mysql連接池的實現
來源:http://blog.csdn.net/kenkao/article/details/47857795
總結
以上是生活随笔為你收集整理的Go操作mysql实现增删改查及连接池的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: golang连接mysql操作示例增删改
- 下一篇: 女孩叫仪好吗 最后一个字是仪的名字