Golang中MySQL连接管理的技巧与实践

Golang中MySQL连接管理的技巧与实践 我编写了一个目前执行几个简单功能的服务。我们有一个网页应用,在页面顶部显示菜单列表。我没有直接在PHP中访问数据库,而是决定用Go语言来测试实现。

我有一个全局的数据库变量用于维护数据库连接。

我编写了一个初始化函数来建立连接,并创建一些后续使用的数据库预处理语句。

主函数负责设置监听器并等待连接。

  1. 数据库连接在所有传入连接间共享,我原本认为这是错误的做法,但有人告诉我这是正确的方式。这种说法正确吗?还是说我应该在每个传入请求中创建新的连接?

  2. 如果确实需要在每次调用时创建新连接,那么该如何处理预处理语句?它们似乎只能在活跃的数据库连接上创建。

  3. 我遇到的主要问题是:如果让服务闲置几分钟,它会报告数据库连接已丢失。随后它似乎会重新建立连接并开始工作,但如果不保持活跃状态,它就会失去连接,最终返回错误信息而不是菜单列表。

我正在努力理解整个数据库管理机制,以及如何在Go中有效管理它,因为目前看来Go在这方面表现得不太稳定。

谢谢。


更多关于Golang中MySQL连接管理的技巧与实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

目前,我已在初始化函数中添加了 db.SetMaxIdleConns(0),这似乎解决了数据库连接丢失的问题。仍在测试其稳定性。

谢谢。

更多关于Golang中MySQL连接管理的技巧与实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,会话通过连接池自动复用。您可以控制此行为并设置会话长度等参数:http://go-database-sql.org/connection-pool.html

这是您要找的信息吗?

在Go语言中管理MySQL连接是一个常见但需要细致处理的问题。以下针对你的具体问题提供技术解答和示例代码。

1. 数据库连接共享的正确性

正确做法是共享连接,但通过连接池管理。在Go中,database/sql包内置了连接池机制,你应该使用单个*sql.DB实例在多个goroutine间共享。

var db *sql.DB

func initDB() error {
    var err error
    db, err = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    if err != nil {
        return err
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    return db.Ping()
}

每个请求不应该创建新连接,这会严重影响性能。sql.DB会自动管理连接池,按需创建、复用和关闭连接。

2. 预处理语句的处理

预处理语句应该与连接分离,使用sql.DBPrepare方法创建的*sql.Stmt是并发安全的,可以在多个goroutine中使用:

var menuStmt *sql.Stmt

func initStatements() error {
    var err error
    menuStmt, err = db.Prepare("SELECT id, name FROM menus WHERE active = ?")
    return err
}

func getMenus(active bool) ([]Menu, error) {
    rows, err := menuStmt.Query(active)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var menus []Menu
    for rows.Next() {
        var menu Menu
        if err := rows.Scan(&menu.ID, &menu.Name); err != nil {
            return nil, err
        }
        menus = append(menus, menu)
    }
    return menus, nil
}

3. 连接丢失问题解决方案

连接超时是MySQL服务器的默认行为。你需要:

设置合理的连接参数:

func initDB() error {
    db, err := sql.Open("mysql", 
        "user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true&timeout=30s&readTimeout=30s&writeTimeout=30s")
    if err != nil {
        return err
    }
    
    // 连接池配置
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(time.Hour) // 小于MySQL的wait_timeout
    db.SetConnMaxIdleTime(10 * time.Minute)
    
    return db.Ping()
}

添加连接健康检查:

func healthCheck() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    
    for range ticker.C {
        if err := db.Ping(); err != nil {
            log.Printf("Database ping failed: %v", err)
        }
    }
}

// 在main函数中启动
go healthCheck()

完整的示例实现:

package main

import (
    "database/sql"
    "log"
    "net/http"
    "time"
    
    _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB
var menuStmt *sql.Stmt

type Menu struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func initDB() error {
    var err error
    db, err = sql.Open("mysql", 
        "user:password@tcp(localhost:3306)/appdb?parseTime=true&timeout=30s")
    if err != nil {
        return err
    }
    
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(time.Hour)
    db.SetConnMaxIdleTime(10 * time.Minute)
    
    return db.Ping()
}

func initStatements() error {
    var err error
    menuStmt, err = db.Prepare("SELECT id, name FROM menus WHERE active = ?")
    return err
}

func menuHandler(w http.ResponseWriter, r *http.Request) {
    menus, err := getMenus(true)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    // 返回菜单数据
    w.Header().Set("Content-Type", "application/json")
    // JSON编码和输出...
}

func getMenus(active bool) ([]Menu, error) {
    rows, err := menuStmt.Query(active)
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var menus []Menu
    for rows.Next() {
        var menu Menu
        if err := rows.Scan(&menu.ID, &menu.Name); err != nil {
            return nil, err
        }
        menus = append(menus, menu)
    }
    return menus, nil
}

func main() {
    if err := initDB(); err != nil {
        log.Fatal("Database initialization failed:", err)
    }
    defer db.Close()
    
    if err := initStatements(); err != nil {
        log.Fatal("Statement preparation failed:", err)
    }
    defer menuStmt.Close()
    
    go func() {
        ticker := time.NewTicker(30 * time.Second)
        defer ticker.Stop()
        for range ticker.C {
            db.Ping() // 保持连接活跃
        }
    }()
    
    http.HandleFunc("/menus", menuHandler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

这种配置可以解决连接丢失问题,同时保持高性能。关键在于正确配置连接池参数和定期健康检查。

回到顶部