探索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創建的鏈接統統都會斷掉,不再占用連接數
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容