Golang中MySQL连接断开后如何实现自动重连

Golang中MySQL连接断开后如何实现自动重连 有没有办法检测到数据库连接丢失,然后重新连接?我需要这样做,因为我正在使用远程 MySQL 数据库…

谢谢。

2 回复

更多关于Golang中MySQL连接断开后如何实现自动重连的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang中实现MySQL连接的自动重连,可以通过以下几种方式:

1. 使用连接池的SetConnMaxLifetime和重连机制

package main

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

type DBManager struct {
    dsn string
    db  *sql.DB
}

func NewDBManager(dsn string) *DBManager {
    return &DBManager{dsn: dsn}
}

func (m *DBManager) Connect() error {
    db, err := sql.Open("mysql", m.dsn)
    if err != nil {
        return err
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute) // 连接最大生命周期
    
    // 测试连接
    if err := db.Ping(); err != nil {
        return err
    }
    
    m.db = db
    return nil
}

func (m *DBManager) GetDB() *sql.DB {
    // 在执行查询前检查连接状态
    if err := m.db.Ping(); err != nil {
        log.Printf("连接断开,尝试重连: %v", err)
        if reconnectErr := m.Connect(); reconnectErr != nil {
            log.Printf("重连失败: %v", reconnectErr)
        } else {
            log.Println("重连成功")
        }
    }
    return m.db
}

// 使用示例
func main() {
    dsn := "user:password@tcp(remote-mysql:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    dbManager := NewDBManager(dsn)
    
    if err := dbManager.Connect(); err != nil {
        log.Fatal(err)
    }
    
    // 执行查询
    var result string
    err := dbManager.GetDB().QueryRow("SELECT 'test'").Scan(&result)
    if err != nil {
        log.Printf("查询失败: %v", err)
    } else {
        fmt.Printf("查询结果: %s\n", result)
    }
}

2. 使用带重试机制的包装函数

package main

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

func WithRetry(db *sql.DB, maxRetries int, fn func(*sql.DB) error) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        err := fn(db)
        if err == nil {
            return nil
        }
        
        lastErr = err
        
        // 检查是否是连接错误
        if isConnectionError(err) {
            log.Printf("连接错误,尝试重连 (尝试 %d/%d)", i+1, maxRetries)
            
            // 关闭旧连接
            db.Close()
            
            // 创建新连接
            newDB, err := sql.Open("mysql", "user:password@tcp(remote-mysql:3306)/dbname")
            if err != nil {
                log.Printf("创建新连接失败: %v", err)
                continue
            }
            
            // 更新引用
            *db = *newDB
            
            // 等待后重试
            time.Sleep(time.Duration(i+1) * time.Second)
            continue
        }
        
        // 非连接错误,直接返回
        return err
    }
    
    return lastErr
}

func isConnectionError(err error) bool {
    // 这里可以根据具体的错误类型判断
    return err != nil && (err == sql.ErrConnDone || err.Error() == "driver: bad connection")
}

// 使用示例
func main() {
    db, err := sql.Open("mysql", "user:password@tcp(remote-mysql:3306)/dbname")
    if err != nil {
        log.Fatal(err)
    }
    
    err = WithRetry(db, 3, func(db *sql.DB) error {
        var count int
        return db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
    })
    
    if err != nil {
        log.Printf("最终执行失败: %v", err)
    }
}

3. 使用健康检查和定期重连

package main

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

type AutoReconnectDB struct {
    dsn         string
    db          *sql.DB
    stopChan    chan struct{}
}

func NewAutoReconnectDB(dsn string) *AutoReconnectDB {
    return &AutoReconnectDB{
        dsn:      dsn,
        stopChan: make(chan struct{}),
    }
}

func (a *AutoReconnectDB) Start() error {
    if err := a.connect(); err != nil {
        return err
    }
    
    go a.healthCheck()
    return nil
}

func (a *AutoReconnectDB) connect() error {
    db, err := sql.Open("mysql", a.dsn)
    if err != nil {
        return err
    }
    
    if err := db.Ping(); err != nil {
        return err
    }
    
    a.db = db
    return nil
}

func (a *AutoReconnectDB) healthCheck() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            if err := a.db.Ping(); err != nil {
                log.Printf("健康检查失败,尝试重连: %v", err)
                if reconnectErr := a.connect(); reconnectErr != nil {
                    log.Printf("重连失败: %v", reconnectErr)
                } else {
                    log.Println("重连成功")
                }
            }
        case <-a.stopChan:
            return
        }
    }
}

func (a *AutoReconnectDB) GetDB() *sql.DB {
    return a.db
}

func (a *AutoReconnectDB) Close() {
    close(a.stopChan)
    if a.db != nil {
        a.db.Close()
    }
}

// 使用示例
func main() {
    dsn := "user:password@tcp(remote-mysql:3306)/dbname"
    autoDB := NewAutoReconnectDB(dsn)
    
    if err := autoDB.Start(); err != nil {
        log.Fatal(err)
    }
    defer autoDB.Close()
    
    // 正常使用数据库
    db := autoDB.GetDB()
    // ... 执行数据库操作
}

这些方法可以有效地检测和处理MySQL连接断开的情况,并自动重新建立连接。第一种方法在每次查询前检查连接状态,第二种方法在执行失败时重试,第三种方法通过定期健康检查来维持连接。

回到顶部