面试题答案
一键面试- 使用defer语句确保连接正确归还
在Go语言中,可以使用
defer
语句在函数结束时执行特定的操作。对于数据库连接池,在获取连接后,使用defer
语句确保连接在函数结束时归还到连接池。
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/database_name")
if err != nil {
panic(err.Error())
}
defer db.Close()
// 获取连接
conn, err := db.Conn(nil)
if err != nil {
panic(err.Error())
}
defer func() {
// 归还连接
err := conn.Close()
if err != nil {
fmt.Println("Error closing connection:", err)
}
}()
// 使用连接执行数据库操作
_, err = conn.Exec("SELECT * FROM some_table")
if err != nil {
fmt.Println("Error executing query:", err)
}
}
- 并发环境下可能遇到的问题及解决方案
问题
- 连接竞争:多个协程同时获取和归还连接,可能导致连接池状态不一致。
- 资源耗尽:如果连接池中的连接数量有限,高并发情况下可能会出现所有连接都被占用,新的请求无法获取连接的情况。
解决方案
- 互斥锁:使用
sync.Mutex
来保护连接池的状态,确保在同一时间只有一个协程可以修改连接池的状态(如获取或归还连接)。 - 信号量:可以使用
sync.Semaphore
或channel
来限制并发获取连接的数量,避免资源耗尽。例如,使用带缓冲的channel
来表示连接池中的可用连接,channel
的缓冲大小就是连接池的最大连接数。
package main
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go-sql-driver/mysql"
)
type ConnectionPool struct {
db *sql.DB
maxConn int
sem chan struct{}
mu sync.Mutex
inUse int
}
func NewConnectionPool(dsn string, maxConn int) (*ConnectionPool, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
pool := &ConnectionPool{
db: db,
maxConn: maxConn,
sem: make(chan struct{}, maxConn),
}
return pool, nil
}
func (p *ConnectionPool) GetConnection() (*sql.Conn, error) {
p.sem <- struct{}{} // 获取信号量,如果没有可用信号量则阻塞
p.mu.Lock()
p.inUse++
p.mu.Unlock()
conn, err := p.db.Conn(nil)
if err != nil {
p.mu.Lock()
p.inUse--
p.mu.Unlock()
<-p.sem // 释放信号量
return nil, err
}
return conn, nil
}
func (p *ConnectionPool) ReturnConnection(conn *sql.Conn) {
err := conn.Close()
if err != nil {
fmt.Println("Error closing connection:", err)
}
p.mu.Lock()
p.inUse--
p.mu.Unlock()
<-p.sem // 释放信号量
}
在上述代码中,ConnectionPool
结构体包含了数据库连接对象、最大连接数、信号量sem
和互斥锁mu
。GetConnection
方法使用信号量来限制并发获取连接的数量,并使用互斥锁保护连接池的状态。ReturnConnection
方法在归还连接时,同样使用互斥锁保护状态并释放信号量。