探索gorm的连接方式

首先写一段源码:

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的最大连接数时将会报错,服务将不可用。

此时我在想如何解决这个问题,有两个思路:

  1. 共用一个连接
    在顶层作用域专门声明一个全局连接变量来保存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{})
}
  1. 不共用一个链接,每次用完都释放连接,在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创建的链接统统都会断掉,不再占用连接数
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容