Golang中数据库连接打开与关闭的最佳实践

Golang中数据库连接打开与关闭的最佳实践 在使用 database/sql 包进行 sql.Open 和 db.Close() 操作时,最佳实践是什么?我遇到一种场景,需要编写多个函数,每个函数都负责访问数据库并获取数据。在这种情况下,我们是否需要在每个函数中都编写 sql.Open() 和 db.Close() 呢?

请查看以下三个文件以及我的代码大致布局。如果我采用这种方法,遵循最佳实践的最佳可能方式是什么。

  1. main.go
func main() {
router := mux.NewRouter()
controller := controllers.Controller{}router.HandleFunc("/protectedEndpoint", controller.GetStudents).Methods("GET")
router.HandleFunc("/signup", controller.GetBook).Methods("GET")
router.HandleFunc("/login", controller.GetReports).Methods("GET")
}
  1. controller.go
func GetStudents(w http.ResponseWriter, r *http.request){
//fetch request param and call the repository method to fetch data from db
studentId := r.URL.Query().get("id)
repository.GetStudents(studentId)
}

func GetBook(w http.ResponseWriter, r *http.request){
//fetch request param and call the repository method to fetch data from db
bookId := r.URL.Query().get("id)
repository.GetBook(bookId)
}

func GetReports(w http.ResponseWriter, r *http.request){
//fetch request param and call the repository method to fetch data from db
studentId := r.URL.Query().get("id)
repository.GetReports(studentId)
}
  1. repository.go
import database/sql

func GetStudents(studentId int){
db, err := sql.open( driverName, connectionString)
if err != nil {
log.panic(err)
}
defer db.close()
row, err := db.query("select * from student where studentId = :0", studentId)
if err != nil {
log.panic(err)
}
defer row.close()
//iterate through the row
}

func GetBook(bookId int){
db, err := sql.open( driverName, connectionString)
if err != nil {
log.panic(err)
}
defer db.close()
row, err := db.query("select * from book where bookId = :0", bookId)
if err != nil {
log.panic(err)
}
defer row.close()
//iterate through the row
}

func GetReports(studentId int){
db, err := sql.open( driverName, connectionString)
if err != nil {
log.panic(err)
}
defer db.close()
row, err := db.query("select * from reports where studentId = :0", studentId)
if err != nil {
log.panic(err)
}
defer row.close()
//iterate through the row

}

更多关于Golang中数据库连接打开与关闭的最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

感谢您的解决方案。但我想知道我们应该在何处关闭连接。因为我们有 NewRepo 函数来创建连接。

更多关于Golang中数据库连接打开与关闭的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在不看到你的代码的情况下,我们很难给出答案。你可能不需要那样做,除非你每次都要连接不同的数据库。

sql.Open 文档的最后一段写道:

返回的 DB 可以安全地被多个 goroutine 并发使用,并维护其自身的空闲连接池。因此,Open 函数应该只调用一次。很少需要关闭 DB。

向我们展示你的代码,我们可以给出更具体的答案。

关于数据库连接和Go,我找到的最佳资源是: 使用Go构建数据库密集型应用的终极指南。 但是,我现在无法下载它。

你可以在这里获取:

图片

Ultimate-Guide-Building-Database-Intensive-Apps-Go-eBook-2019.pdf | Databases…

Scribd是世界上最大的社交阅读和出版网站。

不,只需在 main 函数 -main.go- 中(或在一个包中,然后在 main.go 文件中调用该函数)打开和关闭数据库连接,这样所有函数都会在你连接到数据库后执行。Defer 将在 main 函数执行完其他所有内容后关闭数据库连接。另外,别忘了使用 db.Ping(),就像这样:

// Connect to the database
db, err := sql.Open("driver-name", "database=test1")

// Check errors 
if err != nil {
	log.Fatal(err)
}

// Close connection
defer.Close() 

// Ping verifies a connection to the database is still alive, establishing a connection if necessary
db.Ping()
if err != nil {
    log.fatal(err)
}

如果你不知道 defer 的作用,请查看: https://gobyexample.com/defer

不要每次查询时都打开和关闭一个 sql.DBsql.DB 类型维护着一个连接池,因此后续使用 sql.DB 进行的查询可能会更快,因为它们可能使用现有连接执行。如果你在每个函数调用中都打开和关闭 sql.DB,那么每次进入这些函数时,你都在创建和销毁一个连接池。你应该保留这个连接池,以便后续对 GetStudents 和/或 GetBooks 等的调用可以受益于该连接池。

我建议在 repository.go 中创建一个 Repo(或类似)结构体,如下所示:

type Repo struct {
    db *sql.DB
}

func NewRepo(driverName, connectionString string) (*Repo, error) {
    db, err := sql.Open(driverName, connectionString)
    if err != nil {
        return nil, err
    }
    return &Repo{db: db}, nil
}

然后将你的 GetStudentsGetBook 等函数更改为 Repo 类型的成员函数:

func (r Repo) GetStudents(studentId int){
    row, err := r.db.query(“select * from student where studentId = :0”, studentId)
    if err != nil {
        log.panic(err)
    }
    defer row.close()
    //iterate through the row
}

请查看我的三个文件以及代码的大致结构。如果我采用这种方法,遵循最佳实践的最佳方式是什么?

  1. main.go

    func main() {
    router := mux.NewRouter()
    controller := controllers.Controller{}
    
    router.HandleFunc("/protectedEndpoint", controller.GetStudents).Methods(“GET”)
    router.HandleFunc("/signup", controller.GetBook).Methods(“GET”)
    router.HandleFunc("/login", controller.GetReports).Methods(“GET”)
    }
    
  2. controller.go

    func GetStudents(w http.ResponseWriter, r *http.request){
    //获取请求参数并调用仓库方法从数据库获取数据
    studentId := r.URL.Query().get("id)
    repository.GetStudents(studentId)
    }
    
    func GetBook(w http.ResponseWriter, r *http.request){
    //获取请求参数并调用仓库方法从数据库获取数据
    bookId := r.URL.Query().get("id)
    repository.GetBook(bookId)
    }
    
    func GetReports(w http.ResponseWriter, r *http.request){
    //获取请求参数并调用仓库方法从数据库获取数据
    studentId := r.URL.Query().get("id)
    repository.GetReports(studentId)
    }
    
  3. repository.go

    import database/sql
    
    func GetStudents(studentId int){
    db, err := sql.open( driverName, connectionString)
    if err != nil {
    log.panic(err)
    }
    defer db.close()
    row, err := db.query(“select * from student where studentId = :0”, studentId)
    if err != nil {
    log.panic(err)
    }
    defer row.close()
    //遍历行
    }
    
    func GetBook(bookId int){
    db, err := sql.open( driverName, connectionString)
    if err != nil {
    log.panic(err)
    }
    defer db.close()
    row, err := db.query(“select * from book where bookId = :0”, bookId)
    if err != nil {
    log.panic(err)
    }
    defer row.close()
    //遍历行
    }
    
    func GetReports(studentId int){
    db, err := sql.open( driverName, connectionString)
    if err != nil {
    log.panic(err)
    }
    defer db.close()
    row, err := db.query(“select * from reports where studentId = :0”, studentId)
    if err != nil {
    log.panic(err)
    }
    defer row.close()
    //遍历行
    
    }
    

在Golang中,最佳实践是在应用启动时创建单个数据库连接池,并在整个应用生命周期中复用这个连接池。sql.Open()实际上创建的是一个连接池,而不是单个连接。每个函数都调用sql.Open()db.Close()会导致性能问题和资源浪费。

以下是改进后的代码结构:

main.go

package main

import (
    "database/sql"
    "log"
    "net/http"
    
    "github.com/gorilla/mux"
    "your-project/controllers"
    "your-project/repository"
)

var db *sql.DB

func main() {
    // 初始化数据库连接
    var err error
    db, err = sql.Open("driverName", "connectionString")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // 测试连接
    err = db.Ping()
    if err != nil {
        log.Fatal(err)
    }
    
    // 设置连接池参数
    db.SetMaxOpenConns(25)
    db.SetMaxIdleConns(25)
    db.SetConnMaxLifetime(5 * time.Minute)
    
    // 将db实例传递给repository层
    repo := repository.NewRepository(db)
    controller := controllers.NewController(repo)
    
    router := mux.NewRouter()
    router.HandleFunc("/protectedEndpoint", controller.GetStudents).Methods("GET")
    router.HandleFunc("/signup", controller.GetBook).Methods("GET")
    router.HandleFunc("/login", controller.GetReports).Methods("GET")
    
    log.Fatal(http.ListenAndServe(":8080", router))
}

repository.go

package repository

import (
    "database/sql"
    "log"
)

type Repository struct {
    db *sql.DB
}

func NewRepository(db *sql.DB) *Repository {
    return &Repository{db: db}
}

func (r *Repository) GetStudents(studentId int) {
    row, err := r.db.Query("SELECT * FROM student WHERE studentId = ?", studentId)
    if err != nil {
        log.Printf("Query error: %v", err)
        return
    }
    defer row.Close()
    
    // 处理结果
    for row.Next() {
        // 扫描数据
    }
}

func (r *Repository) GetBook(bookId int) {
    row, err := r.db.Query("SELECT * FROM book WHERE bookId = ?", studentId)
    if err != nil {
        log.Printf("Query error: %v", err)
        return
    }
    defer row.Close()
    
    // 处理结果
    for row.Next() {
        // 扫描数据
    }
}

func (r *Repository) GetReports(studentId int) {
    row, err := r.db.Query("SELECT * FROM reports WHERE studentId = ?", studentId)
    if err != nil {
        log.Printf("Query error: %v", err)
        return
    }
    defer row.Close()
    
    // 处理结果
    for row.Next() {
        // 扫描数据
    }
}

controller.go

package controllers

import (
    "net/http"
    "strconv"
    
    "your-project/repository"
)

type Controller struct {
    repo *repository.Repository
}

func NewController(repo *repository.Repository) *Controller {
    return &Controller{repo: repo}
}

func (c *Controller) GetStudents(w http.ResponseWriter, r *http.Request) {
    studentIdStr := r.URL.Query().Get("id")
    studentId, err := strconv.Atoi(studentIdStr)
    if err != nil {
        http.Error(w, "Invalid student ID", http.StatusBadRequest)
        return
    }
    
    c.repo.GetStudents(studentId)
    // 处理响应
}

func (c *Controller) GetBook(w http.ResponseWriter, r *http.Request) {
    bookIdStr := r.URL.Query().Get("id")
    bookId, err := strconv.Atoi(bookIdStr)
    if err != nil {
        http.Error(w, "Invalid book ID", http.StatusBadRequest)
        return
    }
    
    c.repo.GetBook(bookId)
    // 处理响应
}

func (c *Controller) GetReports(w http.ResponseWriter, r *http.Request) {
    studentIdStr := r.URL.Query().Get("id")
    studentId, err := strconv.Atoi(studentIdStr)
    if err != nil {
        http.Error(w, "Invalid student ID", http.StatusBadRequest)
        return
    }
    
    c.repo.GetReports(studentId)
    // 处理响应
}

这种架构的优势:

  1. 连接池在应用启动时创建一次,避免重复开销
  2. 通过依赖注入将数据库连接传递给repository层
  3. 连接池参数可配置,优化性能
  4. 资源管理更高效,避免频繁打开关闭连接
  5. 支持连接复用,提高并发性能

注意:sql.DB是并发安全的,可以在多个goroutine中安全使用。defer db.Close()应该只在main函数中调用,在应用退出时关闭所有连接。

回到顶部