Golang中如何从连接处理程序访问数据库

Golang中如何从连接处理程序访问数据库 我正在开发一个简单的服务器,用于接收来自多个设备的数据。服务器监听传入连接,并使用 goroutine 处理它们,代码如下:

listener, err := net.Listen("tcp", addr)
if err != nil {
	return err
}
defer listener.Close()

for {
    conn, err := listener.Accept()
    if err != nil {
        continue
    }
    go handleConnection(conn)
}

我想将接收到的数据存储到数据库中,因此我定义了一个全局的 db 变量,并编写了一些辅助函数来保存数据,类似这样:

var db *sql.DB

func InitDB(dataSourceName string) {
    db, err = sql.Open("mssql", dataSourceName)
    if err != nil {
        log.Panic(err)
    }
    if err = db.Ping(); err != nil {
        log.Panic(err)
    }
}

func SaveData() (MyData, error) {
    tx, err := db.Begin()
    if err != nil {
	log.Fatal(err)
    }
    defer tx.Rollback()
    stmt, err := tx.Prepare("INSERT INTO mytable VALUES (?)")
    if err != nil {
        log.Fatal(err)
    }
    defer stmt.Close()
    _, err = stmt.Exec(MyData.Data)
    if err != nil {
        log.Fatal(err)
    }
    err = tx.Commit()
    if err != nil {
        log.Fatal(err)
    }
}

在连接处理程序中,我直接调用辅助函数来操作数据库。这种做法是否正确?我找到了这篇博客文章,其中描述了类似的方法,所以我认为这是可行的。但有一点我仍然不清楚,在这种情况下如何正确关闭数据库?


更多关于Golang中如何从连接处理程序访问数据库的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

这并非可测试的代码,但如果它能实现你的需求,那也没问题 😊 另外请尽量避免使用全局变量。

更多关于Golang中如何从连接处理程序访问数据库的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


当应用程序退出时,您需要关闭 sql.DB 数据库连接池。因此通常您的 main() 函数会打开 sql.DB 并执行延迟关闭,就像处理 TCP 监听器那样。

也就是说,当应用程序退出时,数据库连接显然无论如何都会被关闭。关键在于您是否希望捕获并记录数据库关闭期间发生的任何错误。这样做是个好主意,通过这种方式您可以发现诸如未关闭语句等问题,特别是当底层数据库对此要求严格时。

在Golang中通过全局变量访问数据库是可行的,但需要注意并发安全和资源管理。以下是一个改进的实现示例:

var db *sql.DB

func InitDB(dataSourceName string) error {
    var err error
    db, err = sql.Open("mssql", dataSourceName)
    if err != nil {
        return err
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    return db.Ping()
}

func SaveData(data string) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    
    defer func() {
        if err != nil {
            tx.Rollback()
        }
    }()
    
    stmt, err := tx.Prepare("INSERT INTO mytable (data_column) VALUES (?)")
    if err != nil {
        return err
    }
    defer stmt.Close()
    
    _, err = stmt.Exec(data)
    if err != nil {
        return err
    }
    
    return tx.Commit()
}

func handleConnection(conn net.Conn) {
    defer conn.Close()
    
    // 读取数据
    buf := make([]byte, 1024)
    n, err := conn.Read(buf)
    if err != nil {
        log.Printf("读取连接错误: %v", err)
        return
    }
    
    data := string(buf[:n])
    
    // 保存到数据库
    if err := SaveData(data); err != nil {
        log.Printf("保存数据错误: %v", err)
        return
    }
    
    log.Printf("数据保存成功: %s", data)
}

// 在主函数中优雅关闭数据库
func main() {
    // 初始化数据库
    if err := InitDB("your-connection-string"); err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 启动服务器
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()
    
    // 处理信号,实现优雅关闭
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
    
    go func() {
        <-stop
        log.Println("收到关闭信号,正在关闭服务器...")
        listener.Close()
        db.Close()
        os.Exit(0)
    }()
    
    for {
        conn, err := listener.Accept()
        if err != nil {
            if errors.Is(err, net.ErrClosed) {
                break
            }
            log.Printf("接受连接错误: %v", err)
            continue
        }
        go handleConnection(conn)
    }
}

关键点说明:

  1. 数据库关闭:使用 defer db.Close() 确保程序退出时关闭数据库连接
  2. 连接池配置:通过 SetMaxOpenConns 等方法优化数据库连接池
  3. 错误处理:在 SaveData 函数中返回错误而不是直接调用 log.Fatal
  4. 优雅关闭:通过信号处理实现服务器的优雅关闭
  5. 并发安全sql.DB 是并发安全的,可以在多个 goroutine 中共享使用

这种模式适合大多数应用场景,全局 db 变量在连接处理程序中可以直接使用。

回到顶部