首先寫一段源碼:
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{})
}
首先將程序跑起來,然后每請求一次,用
show processlist;
查看mysql的連接數會不會增加,以此確定gorm.Open函數會不會復用已有的鏈接
請求了4次后的效果:
image.png
很明顯,gorm.Open函數并不會復用已有的鏈接,而是每次調用后就多創建一個鏈接
于是我將mysql的最大連接數設置為5,然后重啟mysql,再次用請求go程序,剛開始都正常,
等到我第5次請求的時候gorm.Open返回值的第二個參數為:
Error 1040: Too many connections
顯然,當連接數超出mysql的最大連接數時將會報錯,服務將不可用。
此時我在想如何解決這個問題,有兩個思路:
- 共用一個連接
在頂層作用域專門聲明一個全局連接變量來保存gorm.Open返回的鏈接,以后請求都復用這個鏈接
問題:程序跑起來之后沒日沒夜地運行,萬一運行過程中這個鏈接由于某些原因斷開了呢,
比如長時間不活躍,mysql直接把它關閉了;或者數據庫重啟等。答案是當這些情況發生時,golang在接下來的第一次請求中無法獲取到數據,此后便可正常工作。
解決方法:給全局連接設置連接可復用的最大時間(這個最大時間應小于mysql服務器設置的wait_timeout):
global_db, err = getdb()
sqlDB, _ := global_db.DB()
// 5秒內連接沒有活躍的話則自動關閉連接
sqlDB.SetConnMaxLifetime(time.Second * 5)
到達最大時間后,gorm會自動關閉連接,此時在mysql用show processlist命令已經查詢不到go創建的鏈接了;
在下次請求時,gorm會自動建立新連接而不需再次調用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秒內連接沒有活躍的話則自動關閉連接
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{})
}
- 不共用一個鏈接,每次用完都釋放連接,在handler加入以下代碼
問題:每次使用后都需要手動釋放鏈接defer func() { if sqlDB, err := db.DB(); err == nil { sqlDB.Close() } }()
但是這樣做依然會有一個問題:現在是全部請求共用一個數據庫鏈接,后面的請求必須等待前一個請求執行完數據庫訪問才能接著進行數據訪問,會有阻塞的可能發生:
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,
})
}
額外的發現:
- 關閉go程序后,go創建的鏈接統統都會斷掉,不再占用連接數