golang mysql的連接池的具體使用
1.mysql-通過(guò)sql建立連接池
連接池用sql.Open函數(shù)創(chuàng)建連接池,可是此時(shí)只是初始化了連接池,并沒(méi)有創(chuàng)建任何連接。連接創(chuàng)建都是惰性的,只有當(dāng)你真正使用到連接的時(shí)候,連接池才會(huì)創(chuàng)建連接。連接池很重要,它直接影響著你的程序行為。
連接池的工作原來(lái)卻相當(dāng)簡(jiǎn)單。當(dāng)你的函數(shù)(例如Exec,Query)調(diào)用需要訪問(wèn)底層數(shù)據(jù)庫(kù)的時(shí)候,函數(shù)首先會(huì)向連接池請(qǐng)求一個(gè)連接。如果連接池有空閑的連接,則返回給函數(shù)。否則連接池將會(huì)創(chuàng)建一個(gè)新的連接給函數(shù)。一旦連接給了函數(shù),連接則歸屬于函數(shù)。函數(shù)執(zhí)行完畢后,要不把連接所屬權(quán)歸還給連接池,要么傳遞給下一個(gè)需要連接的(Rows)對(duì)象,最后使用完連接的對(duì)象也會(huì)把連接釋放回到連接池。
請(qǐng)求一個(gè)連接的函數(shù)有好幾種,執(zhí)行完畢處理連接的方式稍有差別,大致如下:
- db.Ping() 調(diào)用完畢后會(huì)馬上把連接返回給連接池。
- db.Exec() 調(diào)用完畢后會(huì)馬上把連接返回給連接池,但是它返回的Result對(duì)象還保留這連接的引用,當(dāng)后面的代碼需要處理結(jié)果集的時(shí)候連接將會(huì)被重用。
- db.Query() 調(diào)用完畢后會(huì)將連接傳遞給sql.Rows類型,當(dāng)然后者迭代完畢或者顯示的調(diào)用.Clonse()方法后,連接將會(huì)被釋放回到連接池。
- db.QueryRow()調(diào)用完畢后會(huì)將連接傳遞給sql.Row類型,當(dāng).Scan()方法調(diào)用之后把連接釋放回到連接池。
- db.Begin() 調(diào)用完畢后將連接傳遞給sql.Tx類型對(duì)象,當(dāng).Commit()或.Rollback()方法調(diào)用后釋放連接。
因?yàn)槊恳粋€(gè)連接都是惰性創(chuàng)建的,如何驗(yàn)證sql.Open調(diào)用之后,sql.DB對(duì)象可用呢?通常使用db.Ping()方法初始化,調(diào)用了Ping之后,連接池一定會(huì)初始化一個(gè)數(shù)據(jù)庫(kù)連接。
連接失敗關(guān)于連接池另外一個(gè)知識(shí)點(diǎn)就是你不必檢查或者嘗試處理連接失敗的情況。當(dāng)你進(jìn)行數(shù)據(jù)庫(kù)操作的時(shí)候,如果連接失敗了,database/sql會(huì)幫你處理。實(shí)際上,當(dāng)從連接池取出的連接斷開(kāi)的時(shí)候,database/sql會(huì)自動(dòng)嘗試重連10次。仍然無(wú)法重連的情況下會(huì)自動(dòng)從連接池再獲取一個(gè)或者新建另外一個(gè)。
連接池配置配置連接池有兩個(gè)的方法:
- db.SetMaxOpenConns(n int) 設(shè)置打開(kāi)數(shù)據(jù)庫(kù)的最大連接數(shù)。包含正在使用的連接和連接池的連接。如果你的函數(shù)調(diào)用需要申請(qǐng)一個(gè)連接,并且連接池已經(jīng)沒(méi)有了連接或者連接數(shù)達(dá)到了最大連接數(shù)。此時(shí)的函數(shù)調(diào)用將會(huì)被block,直到有可用的連接才會(huì)返回。設(shè)置這個(gè)值可以避免并發(fā)太高導(dǎo)致連接mysql出現(xiàn)too many connections的錯(cuò)誤。該函數(shù)的默認(rèn)設(shè)置是0,表示無(wú)限制。
- db.SetMaxIdleConns(n int) 設(shè)置連接池中的保持連接的最大連接數(shù)。默認(rèn)也是0,表示連接池不會(huì)保持釋放會(huì)連接池中的連接的連接狀態(tài):即當(dāng)連接釋放回到連接池的時(shí)候,連接將會(huì)被關(guān)閉。這會(huì)導(dǎo)致連接再連接池中頻繁的關(guān)閉和創(chuàng)建。
對(duì)于連接池的使用依賴于你是如何配置連接池,如果使用不當(dāng)會(huì)導(dǎo)致下面問(wèn)題:
- 大量的連接空閑,導(dǎo)致額外的工作和延遲。
- 連接數(shù)據(jù)庫(kù)的連接過(guò)多導(dǎo)致錯(cuò)誤。
- 連接阻塞。
- 連接池有超過(guò)十個(gè)或者更多的死連接,限制就是10次重連。
數(shù)據(jù)庫(kù)標(biāo)準(zhǔn)接口里面有3個(gè)方法用于設(shè)置連接池的屬性: SetConnMaxLifetime, SetMaxIdleConns, SetMaxOpenConns
- SetConnMaxLifetime: 設(shè)置一個(gè)連接的最長(zhǎng)生命周期,因?yàn)閿?shù)據(jù)庫(kù)本身對(duì)連接有一個(gè)超時(shí)時(shí)間的設(shè)置,如果超時(shí)時(shí)間到了數(shù)據(jù)庫(kù)會(huì)單方面斷掉連接,此時(shí)再用連接池內(nèi)的連接進(jìn)行訪問(wèn)就會(huì)出錯(cuò), 因此這個(gè)值往往要小于數(shù)據(jù)庫(kù)本身的連接超時(shí)時(shí)間
- SetMaxIdleConns: 連接池里面允許Idel的最大連接數(shù), 這些Idel的連接 就是并發(fā)時(shí)可以同時(shí)獲取的連接,也是用完后放回池里面的互用的連接, 從而提升性能。
- SetMaxOpenConns: 設(shè)置最大打開(kāi)的連接數(shù),默認(rèn)值為0表示不限制。控制應(yīng)用于數(shù)據(jù)庫(kù)建立連接的數(shù)量,避免過(guò)多連接壓垮數(shù)據(jù)庫(kù)。
項(xiàng)目結(jié)構(gòu)
. +--- go.mod +--- go.sum +--- main.go +--- pool | +--- sql-pool.go
代碼pool
package pool import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log" "time" ) var DB *sql.DB func init() { DB, _ = sql.Open("mysql", "root:root@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local") // 使用本地時(shí)間,即東八區(qū),北京時(shí)間 // set pool params DB.SetMaxOpenConns(2000) DB.SetMaxIdleConns(1000) DB.SetConnMaxLifetime(time.Minute * 60) // mysql default conn timeout=8h, should < mysql_timeout err := DB.Ping() if err != nil { log.Fatalf("database init failed, err: ", err) } log.Println("mysql conn pool has initiated.") } func checkErr(err error) { if err != nil { log.Println(err) panic(err) } } func createTable() { db := DB table := `CREATE TABLE IF NOT EXISTS test.user ( user_id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用戶編號(hào)', 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 = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用戶表'` if _, err := db.Exec(table); err != nil { checkErr(err) } } func insert() { db := DB stmt, err := db.Prepare(`INSERT user (user, age) values (?, ?)`) checkErr(err) res, err := stmt.Exec("Elvis", 26) checkErr(err) id, err := res.LastInsertId() checkErr(err) log.Println(id) } func query() { db := DB 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) } } func queryToMap() { db := DB rows, err := db.Query("SELECT * FROM user") checkErr(err) //字典類型 //構(gòu)造scanArgs、values兩個(gè)數(shù)組,scanArgs的每個(gè)值指向values相應(yīng)值的地址 columns, _ := rows.Columns() scanArgs := make([]interface{}, len(columns)) values := make([]interface{}, len(columns)) for i := range values { scanArgs[i] = &values[i] } for rows.Next() { //將行數(shù)據(jù)保存到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 := DB 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 := DB 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) }
main
package main import ( "fmt" "log" "net/http" . "go-mysql-pool-v1/pool" ) func main() { http.HandleFunc("/pool", pool) log.Println("server is up now...") http.ListenAndServe(":8080", nil) } 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() { err = rows.Scan(scanArgs...) for i, col := range values { if col != nil { record[columns[i]] = string(col.([]byte)) } } } log.Println(record) fmt.Fprintf(w, "finish") } func checkErr(err error) { if err != nil { log.Println(err) panic(err) } }
可以通過(guò)工具ab
測(cè)試連接池性能,用法見(jiàn)最下方注。
2.mysql-gorm 建立連接池
其實(shí)gorm的連接池設(shè)置,底層還是用的database/sql的設(shè)置連接池的方法,無(wú)非就是加一層gorm自身的一些設(shè)置。
以下示例為gorm v2版本,v1版本通過(guò)github.com/jinzhu/gorm,如mysql的驅(qū)動(dòng)導(dǎo)入需要加_
。
代碼結(jié)構(gòu)
. +--- config | +--- config.go | +--- config.json +--- go.mod +--- go.sum +--- main.go +--- pool | +--- gorm-pool.go +--- test.db
config.json
{ "database": { "name": "test", "type": "mysql", "host": "localhost", "port": "3306", "user": "root", "password": "root", "table_prefix": "" } }
config.go
package config import ( "encoding/json" "os" ) type Database struct { Type string `json:"type"` Host string `json:"host"` Port string `json:"port"` User string `json:"user"` Password string `json:"password"` Name string `json:"name"` TablePrefix string `json:"table_prefix"` } var DatabaseSetting = &Database{} type Config struct { Database *Database `json:"database"` } var GlobalConfigSetting = &Config{} func init() { // win path is abs-path, linux -> config.json filePtr, err := os.Open("D:\\demo1\\src\\demo\\demo06\\go-mysql-pool-v2\\config\\config.json") if err != nil { return } defer filePtr.Close() // json decode decoder := json.NewDecoder(filePtr) err = decoder.Decode(GlobalConfigSetting) DatabaseSetting = GlobalConfigSetting.Database }
gorm-pool.go
注意設(shè)置表前綴和單復(fù)數(shù)
package pool import ( "fmt" "go-mysql-pool-v2/config" "gorm.io/driver/mysql" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/schema" "log" "time" ) var db *gorm.DB // gorm v2 func init() { var dbURi string var dialector gorm.Dialector if config.DatabaseSetting.Type == "mysql" { dbURi = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true", config.DatabaseSetting.User, config.DatabaseSetting.Password, config.DatabaseSetting.Host, config.DatabaseSetting.Port, config.DatabaseSetting.Name) dialector = mysql.New(mysql.Config{ DSN: dbURi, // data source name DefaultStringSize: 256, // default size for string fields DisableDatetimePrecision: true, // disable datetime precision, which not supported before MySQL 5.6 DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB SkipInitializeWithVersion: false, // auto configure based on currently MySQL version }) } else if config.DatabaseSetting.Type == "postgres" { dbURi = fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable password=%s", config.DatabaseSetting.Host, config.DatabaseSetting.Port, config.DatabaseSetting.User, config.DatabaseSetting.Name, config.DatabaseSetting.Password) dialector = postgres.New(postgres.Config{ DSN: "user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai", PreferSimpleProtocol: true, // disables implicit prepared statement usage }) } else { // sqlite3 dbURi = fmt.Sprintf("test.db") dialector = sqlite.Open("test.db") } conn, err := gorm.Open(dialector, &gorm.Config{ // 如果不用設(shè)置表前綴及復(fù)數(shù),nil NamingStrategy: schema.NamingStrategy{ TablePrefix: config.DatabaseSetting.TablePrefix, // set table prefix SingularTable: true, // set table singular }, }) if err != nil { log.Println(err.Error()) } sqlDB, err := conn.DB() if err != nil { log.Println("connect db server failed.") } sqlDB.SetMaxOpenConns(100) sqlDB.SetMaxIdleConns(10) sqlDB.SetConnMaxLifetime(600*time.Second) db = conn } // open api func GetDB() *gorm.DB { sqlDB, err := db.DB() if err != nil { log.Println("connect db server failed.") } if err = sqlDB.Ping(); err != nil { sqlDB.Close() } return db }
main
package main import ( "go-mysql-pool-v2/pool" "gorm.io/gorm" "log" ) type Product struct { gorm.Model // default add id stats time, id as primary key Code string Price uint } func main() { log.Println("gorm init...") SetupModel() } func SetupModel() { db := pool.GetDB() // auto migrate db.AutoMigrate(&Product{}) // create record db.Create(&Product{Code: "L1212", Price: 1000}) }
go.mod
module go-mysql-pool-v2 go 1.16 replace go-mysql-pool-v2 => ../go-mysql-pool-v2 require ( gorm.io/driver/mysql v1.3.4 gorm.io/driver/postgres v1.3.4 gorm.io/driver/sqlite v1.3.4 gorm.io/gorm v1.23.5 )
3.連接池相較于單個(gè)client
4.通用連接池
注
ubuntu安裝ab工具,apt-get install apache2-utils // get 一般請(qǐng)求 ab -n 1000 -c 1000 http://xxx // post json請(qǐng)求 ab -n 100000 -c 400 -p tempPara.txt -T application/json http://xxx tempPara.txt內(nèi)容: {"driverId": 17,"pageNo": 1,"pageSize": 20,"status": 1}
參考
到此這篇關(guān)于golang mysql的連接池的具體使用的文章就介紹到這了,更多相關(guān)golang mysql連接池內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Go實(shí)現(xiàn)郵箱驗(yàn)證碼API功能
本文將帶你了解一個(gè)項(xiàng)目如何實(shí)現(xiàn)一個(gè)郵箱驗(yàn)證接口,即一個(gè)可用的發(fā)送郵箱驗(yàn)證碼API和驗(yàn)證驗(yàn)證碼是否正確功能,對(duì)Go實(shí)現(xiàn)郵箱驗(yàn)證碼API詳細(xì)過(guò)程感興趣的朋友一起看看吧2024-06-06使用Golang實(shí)現(xiàn)WebSocket心跳機(jī)制
WebSocket是一種在客戶端和服務(wù)器之間實(shí)現(xiàn)全雙工通信的協(xié)議,它允許實(shí)時(shí)地傳輸數(shù)據(jù),并且比傳統(tǒng)的HTTP請(qǐng)求更加高效,在使用Golang構(gòu)建WebSocket應(yīng)用程序時(shí),一個(gè)重要的考慮因素是如何實(shí)現(xiàn)心跳機(jī)制,所以本文將探討如何使用Golang實(shí)現(xiàn)WebSocket心跳2023-11-11Go語(yǔ)言學(xué)習(xí)之WaitGroup用法詳解
Go語(yǔ)言中的?WaitGroup?和?Java?中的?CyclicBarrier、CountDownLatch?非常類似。本文將詳細(xì)為大家講講WaitGroup的用法,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-06-06線上golang grpc服務(wù)資源泄露問(wèn)題排查
這篇文章主要介紹了線上golang grpc服務(wù)資源泄露問(wèn)題排查,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12詳解golang函數(shù)多返回值錯(cuò)誤處理與error類型
這篇文章主要為大家詳細(xì)介紹了golang中函數(shù)多返回值錯(cuò)誤處理與error類型的相關(guān)知識(shí),文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)吧2023-10-10