首先寫(xiě)一段源碼:
package main
import (
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
}
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
db, err := getdb()
if err != nil {
c.JSON(200, gin.H{
"status": 1,
"msg": err.Error(),
})
}
var user User
db.Raw("SELECT id, username FROM users WHERE id = ?", 1).Scan(&user)
c.JSON(200, gin.H{
"status": 0,
"username": user.Username,
"msg": "查詢成功",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
func getdb() (*gorm.DB, error) {
dsn := "root:Edison3306@tcp(192.168.31.141:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
首先將程序跑起來(lái)勉躺,然后每請(qǐng)求一次,用
show processlist;
查看mysql的連接數(shù)會(huì)不會(huì)增加觅丰,以此確定gorm.Open函數(shù)會(huì)不會(huì)復(fù)用已有的鏈接
請(qǐng)求了4次后的效果:
很明顯饵溅,gorm.Open函數(shù)并不會(huì)復(fù)用已有的鏈接,而是每次調(diào)用后就多創(chuàng)建一個(gè)鏈接
于是我將mysql的最大連接數(shù)設(shè)置為5妇萄,然后重啟mysql蜕企,再次用請(qǐng)求go程序,剛開(kāi)始都正常冠句,
等到我第5次請(qǐng)求的時(shí)候gorm.Open返回值的第二個(gè)參數(shù)為:
Error 1040: Too many connections
顯然轻掩,當(dāng)連接數(shù)超出mysql的最大連接數(shù)時(shí)將會(huì)報(bào)錯(cuò),服務(wù)將不可用懦底。
此時(shí)我在想如何解決這個(gè)問(wèn)題唇牧,有兩個(gè)思路:
- 共用一個(gè)連接
在頂層作用域?qū)iT(mén)聲明一個(gè)全局連接變量來(lái)保存gorm.Open返回的鏈接,以后請(qǐng)求都復(fù)用這個(gè)鏈接
問(wèn)題:程序跑起來(lái)之后沒(méi)日沒(méi)夜地運(yùn)行聚唐,萬(wàn)一運(yùn)行過(guò)程中這個(gè)鏈接由于某些原因斷開(kāi)了呢丐重,
比如長(zhǎng)時(shí)間不活躍,mysql直接把它關(guān)閉了杆查;或者數(shù)據(jù)庫(kù)重啟等扮惦。答案是當(dāng)這些情況發(fā)生時(shí),golang在接下來(lái)的第一次請(qǐng)求中無(wú)法獲取到數(shù)據(jù)亲桦,此后便可正常工作崖蜜。
解決方法:給全局連接設(shè)置連接可復(fù)用的最大時(shí)間(這個(gè)最大時(shí)間應(yīng)小于mysql服務(wù)器設(shè)置的wait_timeout):
global_db, err = getdb()
sqlDB, _ := global_db.DB()
// 5秒內(nèi)連接沒(méi)有活躍的話則自動(dòng)關(guān)閉連接
sqlDB.SetConnMaxLifetime(time.Second * 5)
到達(dá)最大時(shí)間后,gorm會(huì)自動(dòng)關(guān)閉連接,此時(shí)在mysql用show processlist命令已經(jīng)查詢不到go創(chuàng)建的鏈接了;
在下次請(qǐng)求時(shí)烙肺,gorm會(huì)自動(dòng)建立新連接而不需再次調(diào)用gorm.Open
完整的代碼:
package main
import (
"time"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID int `json:"id"`
Username string `json:"username"`
}
var global_db *gorm.DB
func main() {
r := gin.Default()
var err error
global_db, err = getdb()
sqlDB, _ := global_db.DB()
// 5秒內(nèi)連接沒(méi)有活躍的話則自動(dòng)關(guān)閉連接
sqlDB.SetConnMaxLifetime(time.Second * 5)
r.GET("/ping", func(c *gin.Context) {
if err != nil {
c.JSON(200, gin.H{
"status": 1,
"msg": err.Error(),
})
}
var user User
global_db.Raw("SELECT id, username FROM users WHERE id = ?", 1).Scan(&user)
c.JSON(200, gin.H{
"status": 0,
"username": user.Username,
"msg": "查詢成功",
})
})
r.Run()
}
func getdb() (*gorm.DB, error) {
dsn := "root:Edison3306@tcp(192.168.31.141:3306)/testdb?charset=utf8mb4&parseTime=True&loc=Local"
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
- 不共用一個(gè)鏈接纳猪,每次用完都釋放連接桃笙,在handler加入以下代碼
問(wèn)題:每次使用后都需要手動(dòng)釋放鏈接defer func() { if sqlDB, err := db.DB(); err == nil { sqlDB.Close() } }()
但是這樣做依然會(huì)有一個(gè)問(wèn)題:現(xiàn)在是全部請(qǐng)求共用一個(gè)數(shù)據(jù)庫(kù)鏈接,后面的請(qǐng)求必須等待前一個(gè)請(qǐng)求執(zhí)行完數(shù)據(jù)庫(kù)訪問(wèn)才能接著進(jìn)行數(shù)據(jù)訪問(wèn)沙绝,會(huì)有阻塞的可能發(fā)生:
func SqlBlock(c *gin.Context) {
var counter Counter
mysql8.Conn.Raw("select sleep(10)").Scan(&counter)
mysql8.Conn.Raw("SELECT count(id) as count FROM usertb").Scan(&counter)
c.JSON(http.StatusOK, gin.H{
"status": 0,
})
}
額外的發(fā)現(xiàn):
- 關(guān)閉go程序后搏明,go創(chuàng)建的鏈接統(tǒng)統(tǒng)都會(huì)斷掉,不再占用連接數(shù)