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

xingwangc2014 · · 1366 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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 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到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


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1366 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传