Go语言官方没有提供数据库驱动,而是为开发数据库驱动定义了标准接口database/sql,开发者能够根据database/sql接口来开发相应的数据库驱动,只要是按照标准接口database/sql开发的代码,之后须要迁移数据库时,不须要任何修改。mysql
sql.Register函数用来注册数据库驱动,第三方开发者开发数据库驱动时,会在init函数内调用sql.Register完成本驱动的注册。sql
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 }
Go-SQL-Driver/MySQL数据库驱动的实现以下:数据库
func init() { sql.Register("mysql", &MySQLDriver{}) }
第三方数据库驱动一般经过调用sql.Register函数来注册本身的数据库驱动名称以及相应的driver实现。在database/sql内部经过一个map来存储用户定义的相应驱动。var drivers = make(map[string]driver.Driver)
缓存
Driver是一个数据库驱动的接口,定义了一个Open(name string)方法,返回一个数据库的Conn接口。session
type Driver interface { Open(name string) (Conn, error) }
Conn只能用来进行一次goroutine操做,即Conn应用于多个goroutine。
第三方驱动都会实现driver.Driver接口,Open方法会解析name参数来获取相关数据库的链接信息,解析完成后,使用此链接信息来初始化一个Conn并返回。ide
driver.Conn是一个数据库链接的接口,定义了一系列方法,Conn只能应用在一个goroutine中,不能使用在多个goroutine中。函数
type Conn interface { Prepare(query string) (Stmt, error) Close() error Begin() (Tx, error) }
Prepare函数返回与当前链接相关的执行Sql语句的准备状态,能够进行查询、删除等操做。
Close函数关闭当前的链接,执行释放链接拥有的资源等清理工做。一般第三方数据库驱动实现了database/sql建议的链接池,开发者没必要去实现缓存链接。
Begin函数返回一个表明事务处理的Tx,经过Tx能够进行查询、更新等操做或者对事务进行回滚、递交。ui
driver.Stmt是一种准备好的状态,与Conn相关联,并且只能应用于一个goroutine中,不能应用于多个goroutine。debug
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结果集code
driver.Tx是事务接口,包含Commit、Rollback方法,数据库驱动只需实现Commit、Rollback函数便可。
type Tx interface { Commit() error Rollback() error }
Go-SQL-Driver/MySQL数据库驱动的实现以下:
type mysqlTx struct { mc *mysqlConn } func (tx *mysqlTx) Commit() (err error) { if tx.mc == nil || tx.mc.closed.IsSet() { return ErrInvalidConn } err = tx.mc.exec("COMMIT") tx.mc = nil return } func (tx *mysqlTx) Rollback() (err error) { if tx.mc == nil || tx.mc.closed.IsSet() { return ErrInvalidConn } err = tx.mc.exec("ROLLBACK") tx.mc = nil return }
driver.Execer是一个Conn可选择实现的接口。
type Execer interface { Exec(query string, args []Value) (Result, error) }
若是第三方数据库驱动没有实现driver.Execer接口,调用DB.Exec会首先调用Prepare返回Stmt,而后执行Stmt的Exec,而后关闭Stmt。
driver.Result是执行Update/Insert等操做返回的结果接口。
type Result interface { LastInsertId() (int64, error) RowsAffected() (int64, error) }
LastInsertId函数返回由数据库执行插入操做获得的自增ID号。
RowsAffected函数返回query操做影响的数据条目数。
driver.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是int64的别名,但实现了Result接口,用来底层实现Result的表示方式。
type RowsAffected int64 var _ Result = RowsAffected(0) func (RowsAffected) LastInsertId() (int64, error) { return 0, errors.New("no LastInsertId available") } func (v RowsAffected) RowsAffected() (int64, error) { return int64(v), nil }
driver.Value是空接口,是数据库驱动必须可以操做的Value,Value能够是nil,int64,float64,bool,[]bytestring,time.Time。type Value interface{}
driver.ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口
type ValueConverter interface { // ConvertValue converts a value to a driver Value. ConvertValue(v interface{}) (Value, error) }
driver.ValueConverter接口实现以下:
type stringType struct{} func (stringType) ConvertValue(v interface{}) (Value, error) { switch v.(type) { case string, []byte: return v, nil } return fmt.Sprintf("%v", v), nil }
数据库驱动开发中,ConvertValue方法用途普遍:
(1)转化driver.value到数据库表相应的字段,例如int64的数据如何转化成数据库表uint16字段。
(2)把数据库查询结果转化成driver.Value值
(3)在scan函数里面如何把driver.Value值转化成用户定义的值
driver.Valuer接口定义一个返回driver.Value的方法。
type Valuer interface { // Value returns a driver Value. Value() (Value, error) }
func Open(driverName, dataSourceName string) (*DB, error) { driversMu.RLock() driveri, ok := drivers[driverName] driversMu.RUnlock() if !ok { return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName) } if driverCtx, ok := driveri.(driver.DriverContext); ok { connector, err := driverCtx.OpenConnector(dataSourceName) if err != nil { return nil, err } return OpenDB(connector), nil } return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil }
Open函数返回DB对象,
type DB struct { connector driver.Connector numClosed uint64 mu sync.Mutex // protects following fields freeConn []*driverConn connRequests map[uint64]chan connRequest nextRequest uint64 // Next key to use in connRequests. numOpen int // number of opened and pending open connections openerCh chan struct{} resetterCh chan *driverConn closed bool dep map[finalCloser]depSet lastPut map[*driverConn]string // stacktrace of last conn's put; debug only maxIdle int // zero means defaultMaxIdleConns; negative means 0 maxOpen int // <= 0 means unlimited maxLifetime time.Duration // maximum amount of time a connection may be reused cleanerCh chan struct{} stop func() // stop cancels the connection opener and the session resetter. }
freeConn是简易的链接池。当执行db.prepare -> db.prepareDC时会defer dc.releaseConn,而后调用db.putConn,把链接放入链接池,每次调用db.conn的时候会先判断freeConn的长度是否大于0,大于0说明有能够复用的conn,若是不大于0,则建立一个conn,而后返回。