Go DB1. database/sql接口

Go没有内置的驱动支持任何的数据库,但是Go定义了database/sql接口,用户可以基于驱动接口开发相应数据库的驱动。

1、sql.Register

注册数据库驱动,当第三方开发者开发数据库驱动时,都会实现init函数,在init里面会调用这个Register(name string, driver driver.Driver)完成本驱动的注册。

我们来看一下mymysql、sqlite3的驱动里面都是怎么调用的:

//https://github.com/mattn/go-sqlite3驱动

func init() {

    sql.Register("sqlite3", &SQLiteDriver{})

}

//https://github.com/mikespook/mymysql驱动

// Driver automatically registered in database/sql

var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}

func init() {

    Register("SET NAMES utf8")

    sql.Register("mymysql", &d)

}

我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。

var drivers = make(map[string]driver.Driver)

drivers[name] = driver

因此通过database/sql的注册函数可以同时注册多个数据库驱动,只要不重复

2、driver.Driver

Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。

type Driver interface {

    Open(name string) (Conn, error)

}

返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误

...

go goroutineA (Conn)  //执行查询操作

go goroutineB (Conn)  //执行插入操作

...

上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱,比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。

第三方驱动都会定义这个函数,它会解析name参数来获取相关数据库的连接信息,解析完成后,它将使用此信息来初始化一个Conn并返回它。

3、driver.Conn

Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面,详情请参考上面的说明。

type Conn interface {

    Prepare(query string) (Stmt, error)

    Close() error

    Begin() (Tx, error)

}

Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。

Close函数关闭当前的连接,执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool,所以你不用再去实现缓存conn之类的,这样会容易引起问题。

Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询、更新等操作,或者对事务进行回滚、递交。

4、driver.Stmt

Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。

type Stmt interface {

    Close() error

    NumInput() int

    Exec(args []Value) (Result, error)

    Query(args []Value) (Rows, error)

}

Close函数关闭当前的链接状态,但是如果当前正在执行query,query还是有效返回rows数据。

NumInput函数返回当前预留参数的个数,当返回>=0时数据库驱动就会智能检查调用者的参数。当数据库驱动包不知道预留参数的时候,返回-1。

Exec函数执行Prepare准备好的sql,传入参数执行update/insert等操作,返回Result数据。

Query函数执行Prepare准备好的sql,传入需要的参数执行select操作,返回Rows结果集。

5、driver.Tx

事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以

type Tx interface {

    Commit() error    // 提交

    Rollback() error    // 回滚

}

这两个函数一个用来递交一个事务,一个用来回滚事务。

6、driver.Execer

这是一个Conn可选择实现的接口

type Execer interface {

    Exec(query string, args []Value) (Result, error)

}

如果这个接口没有定义,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。

7、driver.Result

这个是执行Update/Insert等操作返回的结果接口定义

type Result interface {

    LastInsertId() (int64, error)

    RowsAffected() (int64, error)

}

LastInsertId函数返回由数据库执行插入操作得到的自增ID号。

RowsAffected函数返回query操作影响的数据条目数。

8、driver.Rows

Rows是执行查询返回的结果集接口定义

type Rows interface {

    Columns() []string

    Close() error

    Next(dest []Value) error

}

Columns函数返回查询数据库表的字段信息,这个返回的slice和sql查询的字段一一对应,而不是返回整个表的所有字段。

Close函数用来关闭Rows迭代器。

Next函数用来返回下一条数据,把数据赋值给dest。dest里面的元素必须是driver.Value的值除了string,返回的数据里面所有的string都必须要转换成[]byte。如果最后没数据了,Next函数最后返回io.EOF

9、driver.RowsAffected

RowsAffected其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式

type RowsAffected int64

func (RowsAffected) LastInsertId() (int64, error)

func (v RowsAffected) RowsAffected() (int64, error)

10、driver.Value

Value其实就是一个空接口,他可以容纳任何的数据

type Value interface{}

drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的任意一种:

int64

float64

bool

[]byte

string  [*]除了Rows.Next返回的不能是string.

time.Time

11、driver.ValueConverter

ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口

type ValueConverter interface {

    ConvertValue(v interface{}) (Value, error)

}

在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到,这个ValueConverter有很多好处:

* 转化driver.value到数据库表相应的字段,例如int64的数据如何转化成数据库表uint16字段

* 把数据库查询结果转化成driver.Value值

* 在scan函数里面如何把driver.Value值转化成用户定义的值

12、driver.Valuer

Valuer接口定义了返回一个driver.Value的方式

type Valuer interface {

    Value() (Value, error)

}

很多类型都实现了这个Value方法,用来自身与driver.Value的转化。

13、database/sql

database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn pool。

type DB struct {

    driver driver.Driver

    dsn    string

    mu      sync.Mutex // protects freeConn and closed

    freeConn []driver.Conn

    closed  bool

}

我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行db.prepare -> db.prepareDC的时候会defer dc.releaseConn,然后调用db.putConn,也就是把这个连接放入连接池,每次调用db.conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。

总结

一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容