Golang访问SQL Like数据库(三)——sql package + Postgres driver源码走读

来源:互联网 发布:adobe 软件 编辑:程序博客网 时间:2024/06/03 07:44

github.com/lib/pq

pg是一个纯Go写的Postgres数据库的driver。尼玛,作者给大家开了一个小小的玩笑,pq老是会习惯性的写成pg有没有…安装方式如下:

go get github.com/lib/pq

Register

如前所述,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操作来讲解代码流程。

Sample

首先来一段示例代码(注:代码为伪代码,仅表示流程,不能编译执行):

package mainimport (    "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到driver到sql

前面提到一次数据库操作流程为:

用户代码 --> Sql package -->sql/driver -> database driver -> sql 

import包,注册driver

上面示例中首先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}

User Code –> Sql package -> 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的并发调用相互影响。

User Code ->Sql package -> driver -> Sql

上面示例代码中分别对数据库执行了两次完整的操作,一次是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

0 0