几个数据结构之间的关系
注册数据库
我们使用如下的方式来将一个数据库注册到 beego ORM 的 default alias 中:
RegisterDataBase()
方法的主要内容是将 orm.alias
对象和 sql.DB
,以及对应数据库类型的 dbBaser
对象关联起来。
其中 sql.Open
返回一个 *sql.DB
对象。addAliasWithDB()
方法返回 *orm.alias
,主要是设置 DbBaser
和 DB
这两个对象。DB
指向 sql.DB
,DbBaser
则指向所支持的数据库类型的实现对象。
beego ORM 支持 5 种常用的数据库方言,可以在全局的 dbBasers
map 中查到。
我常用的 Postgres 的实现在 orm/db_postgres.go 里,所依赖的 dbBase
在 orm/db.go 里。
ORM 执行 SQL 语句的过程
一般我们是先调用 NewOrm()
方法获得 orm.Ormer
对象。这个方法会创建一个 orm.orm
对象,主要是设置了 o.alias = al
( al
是上面创建的默认 alias ) 和 o.db = al.DB
。即 o.db
是 *sql.DB
。
然后,当我们要执行一个查询时,我们一般这么写:
这里的 orm.QueryTable
方法,主要是调用 newQuerySet()
返回一个 QuerySetter
interface,本质是一个 *querySet
。
最后,我们执行的 All()
等查询方法,大概是下面这样的:
所以要执行一个 All()
,其实是要到 db_postgres.go 的 dbBasePostgres
里执行 ReadBatch
,也就是在 orm/db.go 的 dbBase
的方法:
这里关注第一个参数 q dbQuerier
,dbQuerier
其实就是确定了需要使用 interface,这些是 *sql.DB
支持的方法的一个子集,ORM 只需要用到这些方法。
到这里,可以简单总结一下通过 ORM 进行数据库查询的主要过程:
- ORM 根据你注册的 ORM 对象以及你指定的 DB alias 生成要执行的 SQL。这些主要由各种
dbBaser
来实现,参考 orm/db_postgres.go 和 orm/db.go。
- ORM 生成了 SQL 之后,调用了
*sql.DB.Query()
方法来执行 SQL。
sql.DB
在执行查询的时候,则需要依赖于注册的 db driver 来实现,例如 Postgres 的 github.com/lib/pq。这一部分本文就不展开说了。
- 得到查询结果之后,再处理成 ORM 对象。
关系图
根据上面的代码分析,可以画一张简单的关系图:
MaxOpenConns and MaxIdleConns
当我们注册一个 alias 的时候,除了 dsn,还会传递参数:MaxOpenConns
和 MaxIdleConns
。这两个参数在 beego ORM 中并没有直接使用,而是为了传递给 sql.DB
对象。你可以看到 ORM 代码中调用了 sql.DB
的 SetMaxIdleConns()
和 SetMaxOpenConns()
两个方法。
这两个参数最终会保存在 sql.DB
中:
这两个参数的作用是:
maxIdle
, MaxIdleConns
: 控制最大空闲连接数。
maxOpen
, MaxOpenConns
: 控制最大连接数。
数据库连接的创建和释放
为了理解 MaxIdleConns
和 MaxOpenConns
,我们先来看一下连接的创建和释放的过程。当我们调用到 sql.DB.Query()
的时候,它会先调用 DB.conn()
方法来获取一个连接:
创建连接
DB.conn()
方法主要做了如下的事情:
- 如果找不到 cache 的 conn,就会尝试建立一个新的 conn。
- 判断
db.numOpen
是否超过了 db.maxOpen
,如果超过了,就挂起等待有连接释放或者关闭。
- 否则,就创建一个新的 conn,然后
db.numOpen++
。
创建连接的方式,主要是调用驱动的方法,不在本文的范围内讨论。DB.conn()
方法如果成功,会返回一个 *driverConn
对象,这个对象代表数据库连接。
释放连接
当一个连接要被释放时,也就是 *driverConn
对象要被释放时,这个对象的 releaseConn()
方法会被调用(上面的 queryDC()
方法的第四个参数就是这个 releaseConn()
方法:
其中的 dc.db.putConn
方法如下:
这里的重点是 added := db.putConnDBLocked(dc, nil)
这行。如果这里返回的 added == false
,那么这个连接就会被关闭,否则就会保留。
这个方法要做几个事情:
- 如果
db.numOpen > db.maxOpen
,那么说明打开的连接数已经超过上限,返回 false
。
- 如果有 goroutine 正在等待请求,那么就会将当前连接分配给那个 goroutine,返回
true
。
- 如果没有 goroutine 正在等待请求,就会判断空闲数量是否达到上限,如果还没有达到,那么就会将连接加入到空闲列表,返回
true
。否则,就会返回 false
。
那么 releaseConn()
什么时候会被调用?这个我们需要看两个地方的代码,首先是 sql.DB.queryDC()
方法的最后一部分:
这个方法返回的 *sql.Rows
对象是查询的行对象,其中的 releaseConn
对象就被赋值为 driverConn.releaseConn()
。接下来,再看下 ORM 里的 ReadBatch()
方法:
当我们从这个方法返回时,也就是我们调用的 querySet.All()
方法返回时,*sql.Rows.Close()
会被调用,其中会调用 releaseConn()
。
小结
maxIdle
表示允许的最大空闲连接,< 0
表示不允许空闲连接, == 0
表示允许两个空闲连接,> 0
表示允许指定的空闲连接。
maxOpen
表示允许的最大连接数,<= 0
表示不限制连接数。
- 在 beego ORM 的实现中,一次查询结束之后,就会释放掉连接。
另外,设置的接口中会保证 maxOpen >= maxIdle
。