Golang中是否应该为每个新请求开启新的数据库连接

Golang中是否应该为每个新请求开启新的数据库连接 这是一个连接数据库的示例,这里我没有关闭数据库,是否应该为每个请求都关闭并重新打开数据库连接?

// 代码内容保持不变
3 回复

database/sql 包中有一个基础连接池,用于复用数据库连接。因此,你可以在需要时打开连接,随后关闭它。关闭操作仅会将连接返还给连接池。

更多关于Golang中是否应该为每个新请求开启新的数据库连接的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


您不需要在每次完成特定事务后关闭每个连接。那样会增加大量不必要的开销。正如 @Yamil_Bracho 所说,系统会为您维护一个连接池,您可以在 database/sql 中的 sql.DB 类型文档中查看:

https://godoc.org/database/sql#DB

DB 是一个数据库句柄,代表零个或多个底层连接的池。它可以安全地被多个 goroutine 并发使用。

sql 包会自动创建和释放连接;同时维护一个空闲连接的自由池。如果数据库具有每个连接状态的概念,则可以在事务(Tx)或连接(Conn)中可靠地观察该状态。一旦调用 DB.Begin,返回的 Tx 将绑定到单个连接。在事务上调用 Commit 或 Rollback 后,该事务的连接将返回到 DB 的空闲连接池。可以使用 SetMaxIdleConns 控制池的大小。

在打开初始数据库连接后,对每个事务使用 sql.Tx 会更简单、更快速。我通过向您的示例添加 dbQuery 函数来说明这一点。当然,您显然需要填写详细信息,但您可以从这里了解其思路:

func main() {
    db, err := sql.Open("postgres", "user=test dbname=test sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    tx, err := db.Begin()
    if err != nil {
        log.Fatal(err)
    }
    defer tx.Rollback()

    // 执行事务操作
    _, err = tx.Exec("INSERT INTO table (column) VALUES ($1)", "value")
    if err != nil {
        log.Fatal(err)
    }

    err = tx.Commit()
    if err != nil {
        log.Fatal(err)
    }
}

此外,建议在每个事务之前对数据库执行 *sqlDB.Ping() 操作。 https://godoc.org/database/sql#DB.Ping

微笑表情

在Go语言中,不应该为每个新请求开启新的数据库连接,也不应该为每个请求关闭并重新打开数据库连接。这样做会导致严重的性能问题和资源浪费。

推荐做法:使用连接池

Go的database/sql包内置了连接池机制,应该在整个应用程序生命周期中维护一个全局的数据库连接实例。

正确的实现方式:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "net/http"
    _ "github.com/lib/pq"
)

var db *sql.DB

func init() {
    var err error
    // 初始化数据库连接池
    db, err = sql.Open("postgres", "user=username password=password dbname=test sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    
    // 配置连接池参数
    db.SetMaxOpenConns(25)      // 最大打开连接数
    db.SetMaxIdleConns(25)      // 最大空闲连接数
    db.SetConnMaxLifetime(5 * time.Minute) // 连接最大生命周期
    
    // 测试连接
    if err = db.Ping(); err != nil {
        log.Fatal(err)
    }
}

func handler(w http.ResponseWriter, r *http.Request) {
    // 在请求处理中使用共享的连接池
    var name string
    err := db.QueryRow("SELECT name FROM users WHERE id = $1", 1).Scan(&name)
    if err != nil {
        http.Error(w, err.Error(), 500)
        return
    }
    
    fmt.Fprintf(w, "User name: %s", name)
}

func main() {
    defer db.Close() // 程序退出时关闭连接池
    
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

连接池的优势:

  1. 性能优化:避免频繁创建和销毁连接的开销
  2. 资源管理:控制同时打开的连接数量
  3. 连接复用:空闲连接可以被后续请求重用
  4. 自动管理database/sql自动处理连接的获取和释放

在请求中正确处理:

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    // 获取查询参数
    userID := r.URL.Query().Get("id")
    
    // 使用连接池执行查询
    rows, err := db.Query("SELECT id, name, email FROM users WHERE id = $1", userID)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer rows.Close() // 重要:关闭rows
    
    for rows.Next() {
        var id int
        var name, email string
        if err := rows.Scan(&id, &name, &email); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        fmt.Fprintf(w, "User: %d, %s, %s\n", id, name, email)
    }
    
    if err = rows.Err(); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

关键点:在整个应用程序中维护一个数据库连接实例,让连接池管理连接的创建、复用和清理。每个请求只需要从连接池中获取连接使用,完成后将连接返回到池中。

回到顶部