pg是一个纯Go写的Postgres数据库的driver。尼玛,作者给大家开了一个小小的玩笑,pq老是会习惯性的写成pg有没有…安装方式如下:
go get github.com/lib/pq
如前所述,Driver需要调用sql.Register根据名字将driver实现的driver.Driver类型interface注册。pq driver的init函数在源码pq/conn.go文件中,init函数调用sql.Register将driver 数据结构注册到名字”postgres“下。
type drv struct{}
func (d *drv)Open(name string)(dirver.Conn, error) {
return Open(name)
}
func init() {
sql.Register("postgres", &drv{})
}
这样在代码中import ”github.com/lib/pq”时postgres将自动被注册。
下面将以一次Query操作来讲解代码流程。
首先来一段示例代码(注:代码为伪代码,仅表示流程,不能编译执行):
package main
import (
"database/sql"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "postgres://user:pwd@server/database?sslmode=disable")
rows, err := db.Query("select * from table")
stmt, err := db.Prepare("update table set name=$1 where id=$2")
res, err := stmt.Exec("xiaoming", 2)
}
前面提到一次数据库操作流程为:
用户代码 --> Sql package -->sql/driver -> database driver -> sql
上面示例中首先import了sql package和pq包。在import pq时指定包别名为”_”表示,我们只是需要通过import pq包自动调用包的init函数注册driver,而不需要直接使用包的任何接口。
sql.Register函数在sql/sql.go文件中,代码如下,Register将driver注册到一个全局的Map[string]driver.Driver中。
var (
driversMu sync.RWMutex
drivers = make(map[string]driver.Driver)
)
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
drivers[name] = driver
}
sql.Open -> db.connectionOpener -> db.openNewConnection
Open首先通过driverName从drivers中查找driver,找到后创建一个DB数据类型,并使用driver赋值给db.driver, dataSourceName赋值给db.dsn初始化新的DB。
然后db.Open调用db.connectionOpener, db.connectionOpener将便利所有的channel,并执行db.openNewConnection.
db.openNewConnection将调用db.driver.Open(即pq.drv.Open)对dataSourceName进行验证,如果dataSourceName中的参数遵循正确的格式,且有足够多可可以用于连接数据库的参数则返回一个driver.Conn接口。
pq中的代码主要流程为:
drv.Open -> Open -> DialOpen
db.openNewConnection在获得driver.Open返回的driver.Conn后将为其包装一个mutex防止对driver.Conn interface的并发调用相互影响。
上面示例代码中分别对数据库执行了两次完整的操作,一次是Query操作,还有一次是Update操作。本文仅对Query流程进行讲解,Update流程类似,读者可自行追代码了解。
sql中的Query的主要流程为:
sql.DB.Query
| -> sql.DB.query
| -> sql.DB.queryConn
| -> driver.Conn.Prepare (dirver)
| -> rowsiFromStatement
| -> driver.Stmt.Query (driver)
Query主要进行连接retry次数的控制,然后调用query执行实际的Query操作。
query首先根据连接类型(默认cachedOrNewConn)从连接池中获取前面Open返回的driver.Conn interface. 如果有相应连接,则调用queryConn执行Query操作。
queryConn在执行一些验证、上锁操作等候,掉用driver。Conn的Prepare准备statement。这里的流程已经成功无缝切到driver部分。在driver层由Prepare函数完成statement准备后,控制权再次返回到sql package中。获取statement类型Stmt后,同样是封装一层后,调用rowsiFromStatement。
rowsiFromStatement进行了一些互斥上锁操作后,通过调用Stmt.Query再次切换到driver层,通过driver具体实现的Query函数对数据库进行操作,读取Rows并返回。
pq中Conn实现的Prepare部分源码如下:
func (cn *conn) Prepare(q string) (_ driver.Stmt, err error) {
if cn.bad {
return nil, driver.ErrBadConn
}
defer cn.errRecover(&err)
if len(q) >= 4 && strings.EqualFold(q[:4], "COPY") {
return cn.prepareCopyIn(q)
}
return cn.prepareTo(q, cn.gname()), nil
}
pq中Stmt实现的Query部分源码如下:
func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
if st.cn.bad {
return nil, driver.ErrBadConn
}
defer st.cn.errRecover(&err)
st.exec(v)
return &rows{
cn: st.cn,
colNames: st.colNames,
colTyps: st.colTyps,
colFmts: st.colFmts,
}, nil
}
https://golang.org/src/database/sql/doc.txt
https://godoc.org/database/sql
https://golang.org/src/database/sql/
https://godoc.org/github.com/lib/pq
https://github.com/lib/pq
https://godoc.org/github.com/lib/pq
http://jmoiron.github.io/sqlx/
https://github.com/jmoiron/sqlx/blob/master/sqlx.go
https://github.com/golang/go/wiki/SQLInterface
https://github.com/golang/go/wiki/SQLDrivers