首先写一段源码:
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次后的效果:
很明显,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创建的链接统统都会断掉,不再占用连接数