GO.数据库接口

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

目前NOSQL已经成为Web开发的一个潮流,很多应用采用了NOSQL作为数据库,而不是以前的缓存,后面将介绍MongoDB和Redis两种NOSQL数据库。

详细分析一下Go都定义了哪些标准接口:

sql.Register

这个存在于database/sql的函数是用来注册数据库驱动的,当第三方开发者开发数据库驱动时,都会实现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的注册函数可以同时注册多个数据库驱动,只要不重复。

在我们使用database/sql接口和第三方库的时候经常看到如下:

import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)

这儿使用_的意思是引入后面的包名而不直接使用这个包中定义的函数,变量等资源。

包在引入的时候会自动调用包的init函数以完成对包的初始化。因此,我们引入上面的数据库驱动包之后不用手动去调用init函数,然后在init函数里面注册这个数据库驱动,这样我们就可以在接下来的代码中直接使用这个数据库驱动了。

driver.Driver

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

type Driver interface {
  Open(name string) (Conn, error)
}

返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。

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

driver.Conn

Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面

type Conn interface {
  Prepare(query string) (Stmt, error)
  Close() error
  Begin() (Tx, error)
}

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

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

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

driver.Stmt

Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个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结果集

driver.Tx

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

type Tx interface {
  Commit() error
  Rollback() error
}

driver.Execer

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

type Execer interface {
  Exec(query string, args []Value) (Result, error)
}

driver.Result

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

type Result interface {
  LastInsertId() (int64, error)
  RowsAffected() (int64, error)
}

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

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

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。

driver.RowsAffected

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

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

driver.Value

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

type Value interface{}

drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的任意一种int64、float64、bool、[]byte、string 、time.Time

driver.ValueConverter

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

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

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

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

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

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

driver.Valuer

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

type Valuer interface {
  Value() (Value, error)
}

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

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

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的时候会defer db.putConn(ci, err),也就是把这个连接放入连接池,每次调用conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。

MySQL驱动

Go中支持MySQL的驱动目前比较多,有如下几种,有些是支持database/sql标准,而有些是采用了自己的实现接口,常用的有如下几种:

https://github.com/Go-SQL-Driver/MySQL 支持database/sql,全部采用go写。

https://github.com/ziutek/mymysql 支持database/sql,也支持自定义的接口,全部采用go写。

https://github.com/Philio/GoMySQL 不支持database/sql,自定义接口,全部采用go写。

接下来的例子以第一个驱动为例:

package main

import (
        "database/sql"
        "fmt"
        _ "github.com/Go-SQL-Driver/MySQL"
)

func main() {
        connection, err := sql.Open("mysql", "aries1991:admin123@/aries?charset=utf8")
        panicErr(err)
        //插入数据
        stmt, err := connection.Prepare("INSERT userinfo SET username=?,departname=?,created=?")
        panicErr(err)
        res, err := stmt.Exec("astaxie", "研发部门", "2012-12-09")
        panicErr(err)
        id, err := res.LastInsertId()
        panicErr(err)
        fmt.Println(id)
        //查询数据
        rows, err := connection.Query("SELECT * FROM userinfo")
        panicErr(err)
        for rows.Next() {
                var uid int
                var username string
                var department string
                var created string
                err = rows.Scan(&uid, &username, &department, &created)
                panicErr(err)
                fmt.Println(uid, username, department, created)
        }

        connection.Close()
}

func panicErr(err error) {
        if err != nil {
                panic(err)
        }
}

解释一下关键几个函数:

sql.Open() 函数用来打开一个注册过的数据库驱动,Go-MySQL-Driver中注册了mysql这个数据库驱动,

第二个参数是DNS(Data Source Name),它是Go-MySQL-Driver定义的一些数据库链接和配置信息。支持如下格式:

user@unix(/path/to/socket)/dbname?charset=utf8

user:password@tcp(localhost:5555)/dbname?charset=utf8

user:password@/dbname

user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

db.Prepare()函数用来返回准备要执行的sql操作,然后返回准备完毕的执行状态。

db.Query()函数用来直接执行Sql返回Rows结果。

stmt.Exec()函数用来执行stmt准备好的SQL语句

NOSQL数据库操作

redis

redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)和zset(有序集合)。

目前应用redis最广泛的应该是新浪微博平台,其次还有Facebook收购的图片社交网站instagram。以及其他一些有名的互联网企业。

Go目前支持redis的驱动有如下 :

https://github.com/alphazero/Go-Redis 、http://code.google.com/p/tideland-rdc、https://github.com/simonz05/godis、https://github.com/astaxie/goredis

//TODO

#笔记内容来自 《Go Web编程》