Go database/sql 基于临时 channel 传递连接

Go 的 database/sql 包中,连接池的管理是其核心功能之一,其中通过临时创建的 channel 来传递连接的方式是一个非常巧妙的设计。这种设计保证了连接的并发安全,同时灵活地管理了连接的获取和归还。

核心逻辑分为以下几个部分:

连接的获取:

  • 当需要一个连接时,尝试从空闲连接池中获取。
  • 如果池中没有可用连接且当前连接数未达到最大限制,则创建新连接。
  • 如果池满了,会通过临时创建的 channel 等待已有连接归还。

连接的归还:

  • 当使用完连接后,将其放回空闲连接池。
  • 如果有等待获取连接的 goroutine,会通过 channel 通知其中一个。

临时的 channel:

  • 如果连接池没有可用连接,会为每个等待连接的请求创建一个临时 channel。
  • 通过这个 channel,将归还的连接传递给等待的请求。
  • 这样可以避免锁的过多竞争,并且实现了高效的等待和通知机制。

以下是 database/sql 源码中与连接获取和归还相关的简化逻辑(部分代码可能略有调整以便更易理解):

go 复制代码
type DB struct {
 freeConn     []*driverConn            // 空闲连接池,当前所有的空闲连接
 connRequests map[int]chan connRequest // 若空闲连接池中无空闲连接,存储当前有哪些协程在申请新连接
 reqKey       int

 mu sync.Mutex // protects following fields
}

func NewDB(size int) *DB {
 freeConn := make([]*driverConn, 0, size)
 for i := 1; i <= size; i++ {
  freeConn = append(freeConn, &driverConn{})
 }
 return &DB{
  freeConn:     freeConn,
  connRequests: make(map[int]chan connRequest),
  reqKey:       0,
 }
}

type driverConn struct { // 表示真正需要的连接
}

type connRequest struct { // 表示申请一个新连接的请求
 conn *driverConn
}

func (db *DB) Conn() *driverConn {
 db.mu.Lock()
 numFree := len(db.freeConn)
 if numFree > 0 { // 当连接池中有空闲连接的时候
  conn := db.freeConn[0] // 取 slice 切片的第一个元素,然后将 slice 后面的元素往前挪,最后通过截断来"释放"最后一个元素。
  copy(db.freeConn, db.freeConn[1:])
  db.freeConn = db.freeConn[:numFree-1]
  fmt.Println("空闲连接池中获得")
  db.mu.Unlock()
  return conn
 }
 req := make(chan connRequest, 1) // 当连接池中没有空闲连接的时候
 reqKey := db.reqKey + 1
 db.connRequests[reqKey] = req
 db.mu.Unlock()
 select {
 case <-time.After(10 * time.Second):
  return nil
 case ret := <-req:
  fmt.Println("已有连接归还中获得")
  return ret.conn
 }
}

func (db *DB) PutConn(dc *driverConn) {
 db.mu.Lock()
 defer db.mu.Unlock()
 if len(db.connRequests) > 0 {
  var req chan connRequest
  var reqKey int
  for reqKey, req = range db.connRequests {
   break
  }
  delete(db.connRequests, reqKey)
  req <- connRequest{
   conn: dc,
  }
  fmt.Println("归还到新请求连接中")
  return
 }
 db.freeConn = append(db.freeConn, dc)
 fmt.Println("归还到空闲连接池")
}

func main() {
 db := NewDB(3) // 创建对象,设置空闲连接池中有3个空闲连接

 go func() {
  dc := db.Conn() // 获取连接
  time.Sleep(2 * time.Second)
  db.PutConn(dc) // 归还连接
 }()

 go db.Conn() // 获取连接
 go db.Conn() // 获取连接
 go db.Conn() // 获取连接

 time.Sleep(10 * time.Second)
}

// Output:
// 空闲连接池中获得
// 空闲连接池中获得
// 空闲连接池中获得
// 归还到新请求连接中
// 已有连接归还中获得
相关推荐
架构技术专栏6 小时前
Agent 工具调用:从工具声明到执行反馈
前端·人工智能·后端
码事漫谈7 小时前
你让AI干的活,它可能只干了十分之一——而你根本看不出来
后端
Wx-bishekaifayuan7 小时前
express皖美特色农产品网售系统53118-计算机课程设计、毕业设计
人工智能·spring boot·后端·python·课程设计·express·旅游
Wx-bishekaifayuan8 小时前
springboot汉中旅游服务平台41442-计算机课程设计、毕业设计
spring boot·后端·python·django·课程设计·express·旅游
WeiXin_DZbishe8 小时前
基于springboot城市公交实时运营与调度系统-计算机毕设 附源码【课程设计】66173
java·vue.js·spring boot·后端·python·php·课程设计
WeiXin_DZbishe8 小时前
基于Springboot+AI的kuall音乐广场的设计与实现-计算机毕设 附源码【课程设计】12715
vue.js·人工智能·spring boot·后端·python·php·课程设计
A接拉起0078 小时前
2026 年 JavaScript 开发者需要了解的知识
前端·javascript·后端
Bazingga9 小时前
RAG评测体系保姆级教程:生产级实践指南
后端
tachibana210 小时前
使用for range 的时候,它的地址会发生变化吗?
开发语言·后端·golang·go·语法
蜗牛互联网10 小时前
GPT-6 与 Intelligent UI:从回答到可审计操作的产品迁移
java·人工智能·后端