Golang中数据库连接打开与关闭的最佳实践
Golang中数据库连接打开与关闭的最佳实践 在使用 database/sql 包进行 sql.Open 和 db.Close() 操作时,最佳实践是什么?我遇到一种场景,需要编写多个函数,每个函数都负责访问数据库并获取数据。在这种情况下,我们是否需要在每个函数中都编写 sql.Open() 和 db.Close() 呢?
请查看以下三个文件以及我的代码大致布局。如果我采用这种方法,遵循最佳实践的最佳可能方式是什么。
- 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")
}
- 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)
}
- 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
感谢您的解决方案。但我想知道我们应该在何处关闭连接。因为我们有 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.DB。sql.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
}
然后将你的 GetStudents、GetBook 等函数更改为 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
}
请查看我的三个文件以及代码的大致结构。如果我采用这种方法,遵循最佳实践的最佳方式是什么?
-
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”) } -
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) } -
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)
// 处理响应
}
这种架构的优势:
- 连接池在应用启动时创建一次,避免重复开销
- 通过依赖注入将数据库连接传递给repository层
- 连接池参数可配置,优化性能
- 资源管理更高效,避免频繁打开关闭连接
- 支持连接复用,提高并发性能
注意:sql.DB是并发安全的,可以在多个goroutine中安全使用。defer db.Close()应该只在main函数中调用,在应用退出时关闭所有连接。

