上图中是 driver.go 中定义的所有接口, 他们为不同实现的数据库引擎定义了相似的使用方法,降低了学习成本
Register()
首先使用一个数据库要先找到它的数据库引擎,比如使用 mysql 就需要先引入 github.com/go-sql-driver/mysql
import _ "github.com/go-sql-driver/mysql"
这里只需要初始化就可以了,因为我们需要将 这个代码包内的 driver
先注册到 drivers
中, drivers
是一个 map[string][driver]
存储这我们需要的 driver
,通过名称作为索引。
Open()
现在 driver 存储在我们的 drivers 中,如果要使用数据库我们应该把它取出来
根据数据库名称将 driver 取出,然后生成 一个 DB,DB 并不一定意味着一定能够连接上数据库了,这得看 driver 的实现了,如果满足 DriverContext 接口的话,可能会在生成 Connector 的时候,检测连接是否能成功建立,如果不满足,只是将 Driver 放入 dsnConnector 中,真正连接的时候还是使用 driver 的 Conn() 方法。
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
在生成 DB 的时候,还起了两个 gorountine,分别使用两个 channel 进行通信,其中一个是建立连接,另一个是将连接恢复为初始状态,方便下次使用的。
conn()
虽然现在已经有了 DB 但是还没有真正操作数据库,所有的操作都是通过 driverConn 进行的,现在我们来看看是怎么生成 driverConn 的。
生成 driverConn 是使用 DB.conn() 函数,这个函数的签名是这样的,我们可以看出这里传入了一个 connReuseStrategy 这是一个 uint8 的值用来表示是否应该使用缓存的 conn
func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error)
type connReuseStrategy uint8
const (
alwaysNewConn connReuseStrategy = iota
cachedOrNewConn
)
首先来看一下新建连接,这个比较简单,直接使用 DB 中准备好的 Connector 获得 driver.Conn 然后生成 driverConn 就可以了。但是如果已有的连接数超出了最大连接,我们就得先进行等待了。等待的时候会先生成一个 channel,然后存入 connRequests 中,如果有连接被释放或者有连接关闭的时候调用 maybeOpenNewConnextions()
都会检查是否这个结构体内存储着需要建立的连接,然后利用生成 DB 的时候建立的 openerCh 生成一个新连接,再通过这个 channel 返回。
需要注意的是,这里用 range 从 connRequests 中取出 req ,但是对 map 使用 range 是随机获取的,并不是按照放入的顺序。
for reqKey, req = range db.connRequests {
break
}
同样如果没有等待的 request,那就把空闲的 driverConn 存到 DB 中的 freeConn 中,下一次就可以直接使用旧的 conn 了。
一般情况下,获取连接都是使用的 cachedOrNewConn,只有在像下面这样,获取连接的次数超过了最大尝试次数,才会直接新建一个连接,这个最大尝试次数是一个常量没法更改,数值是 2
for i := 0; i < maxBadConnRetries; i++ {
dc, err = db.conn(ctx, cachedOrNewConn)
if err != driver.ErrBadConn {
break
}
}
if err == driver.ErrBadConn {
dc, err = db.conn(ctx, alwaysNewConn)
}
if err != nil {
return err
}