面试题答案
一键面试潜在错误
- 连接池管理缺失:代码只是简单地创建了一个数据库连接,并没有实现真正的连接池管理。没有复用连接,每次调用
getDB
实际没有从连接池中获取连接。 - 未处理连接关闭:虽然在
main
函数中有defer db.Close()
,但如果getDB
被多次调用,其他地方没有合适的关闭逻辑,可能导致连接未正确关闭,造成资源泄漏。 - 竞争条件:代码没有考虑到多个 goroutine 同时访问数据库连接的情况,可能会引发竞争条件。
改进代码
package main
import (
"database/sql"
"fmt"
"log"
"sync"
_ "github.com/go - sql - driver/mysql"
)
var (
once sync.Once
db *sql.DB
err error
poolSize = 10
)
func newDBConnectionPool() func() (*sql.DB, error) {
var pool sync.Pool
once.Do(func() {
db, err = sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Fatal(err)
}
db.SetMaxIdleConns(poolSize)
db.SetMaxOpenConns(poolSize)
})
pool.New = func() interface{} {
conn, err := db.Conn(nil)
if err != nil {
log.Fatal(err)
}
return conn
}
return func() (*sql.DB, error) {
conn := pool.Get().(*sql.Conn)
defer func() {
pool.Put(conn)
}()
return db, nil
}
}
func main() {
getDB := newDBConnectionPool()
db, err := getDB()
if err != nil {
log.Fatal(err)
}
defer func() {
if err := db.Close(); err != nil {
log.Println(err)
}
}()
// 执行数据库操作
rows, err := db.Query("SELECT * FROM users")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
// 处理结果
}
}
闭包的作用
闭包在这个过程中封装了获取和释放数据库连接的逻辑,使得代码结构更清晰。通过闭包可以隐藏连接池的具体实现细节,外部调用者只需要调用闭包函数获取连接,而不需要关心连接池内部是如何创建、管理和复用连接的。
可能引入的错误场景
- 资源泄漏:如果在闭包内部没有正确处理连接的释放,例如忘记在使用完连接后放回连接池,就会导致连接一直被占用,最终耗尽资源。
- 竞争条件:当多个 goroutine 同时调用闭包获取连接时,如果没有适当的同步机制,可能会导致连接池状态不一致,例如同时获取到同一个连接,或者连接被错误地重复释放等问题。