第三方库 go-sql-driver 源码浅析
一、go-sql-driver使用过程
1、建立连接
首先是Open,
db, err := sql.Open(“mysql”, “user:password@/dbname”)
db 是一个*sql.DB类型的指针,在后面的操作中,都要用到db
open之后,并没有与数据库建立实际的连接,与数据库建立实际的连接是通过Ping方法完成。此外,db应该在整个程序的生命周期中存在,也就是说,程序一启动,就通过Open获得db,直到程序结束,再Close db,而不是经常Open/Close。
err = db.Ping()
2、基本用法
DB的主要方法有:
Query 执行数据库的Query操作,例如一个Select语句,返回*Rows
QueryRow 执行数据库至多返回1行的Query操作,返回*Row
PrePare 准备一个数据库query操作,返回一个*Stmt,用于后续query或执行。这个Stmt可以被多次执行,或者并发执行
Exec 执行数不返回任何rows的据库语句,例如delete操作
Stmt的主要方法:
Exec
Query
QueryRow
Close
用法与DB类似
Rows的主要方法:
Cloumns//返回[]string,column names
Scan
Next
Close
二、源码分析
1,初始化
golang的源码包里database/sql只定义了连接池和常用接口、数据类型
具体到mysql 的协议实现在
github.com/go-sql-driver/mysql
因此我们需要在使用的时候这样导入依赖
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
这个import做了什么呢
_ "github.com/go-sql-driver/mysql"
我们可以在driver.go里看到下面这个函数
func init() {
sql.Register("mysql", &MySQLDriver{})
}
向sql的驱动里注入了mysql 的实现。
先看下golang 源码中驱动相关的代码,定义在这个文件中:src/database/sql/sql.go
var drivers = make(map[string]driver.Driver)
func Register(name string, driver driver.Driver) {
drivers[name] = driver
}
注册的过程就是将驱动存入这个map
它的value是一个interface,定义在src/database/driver/driver.go这个文件中
type Driver interface {
Open(name string) (Conn, error)
}
只有一个方法,Opne,返回是一个表示连接的interface
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
连接里面有三个方法,其中Prepare返回的是一个interface stmt
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
在stmt中我们常用到的是两个接口Exec和Query,分别返回了Result和Rows
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
回到go-sql-driver,可以看到driver.go里
MySQLDriver实现了type Driver interface
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
cfg, err := ParseDSN(dsn)
return c.Connect(context.Background())
}
而在connection.go,里实现了type Conn interface
type mysqlConn struct {
}
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
err := mc.writeCommandPacketStr(comStmtPrepare, query)
stmt := &mysqlStmt{
mc: mc,
}
columnCount, err := stmt.readPrepareResultPacket()
}
func (mc *mysqlConn) Begin() (driver.Tx, error) {
}
func (mc *mysqlConn) Close() (err error){
}
在statement.go里实现了type Stmt interface
type mysqlStmt struct {
mc *mysqlConn
id uint32
paramCount int
}
func (stmt *mysqlStmt) Close() error
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
}
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
}
func (stmt *mysqlStmt) NumInput() int
在connector.go里初始化了mysqlConn
type connector struct {
cfg *Config // immutable private copy.
}
func (c *connector) Connect(ctx context.Context) (driver.Conn, error){
mc := &mysqlConn{
maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1,
closech: make(chan struct{}),
cfg: c.cfg,
}
nd := net.Dialer{Timeout: mc.cfg.Timeout}
mc.netConn, err = dial(dctx, mc.cfg.Addr)
authResp, err := mc.auth(authData, plugin)
}
func (c *connector) Driver() driver.Driver {
return &MySQLDriver{}
}
而在driver.go的Open方法里调用的正是这个方法c.Connect(context.Background())
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
cfg, err := ParseDSN(dsn)
return c.Connect(context.Background())
}
上面就完成了整个初始化的过程,下面我们来看看连接的过程
2,连接
连接的时候我们调用的是golang源码中的Open函数
func Open(driverName, dataSourceName string) (*DB, error) {
//获取驱动
driveri, ok := drivers[driverName]
connector, err := driverCtx.OpenConnector(dataSourceName)
//连接数据库
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
func OpenDB(c driver.Connector) *DB {
go db.connectionOpener(ctx)
}
// Runs in a separate goroutine, opens new connections when requested.
func (db *DB) connectionOpener(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case <-db.openerCh:
db.openNewConnection(ctx)
}
}
}
func (db *DB) openNewConnection(ctx context.Context) {
ci, err := db.connector.Connect(ctx)
db.maybeOpenNewConnections()
}
func (db *DB) maybeOpenNewConnections() {
db.openerCh <- struct{}{}
}
func (dc *driverConn) finalClose() error {
dc.db.maybeOpenNewConnections()
}
func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
db.maybeOpenNewConnections()
}
func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
db.maybeOpenNewConnections()
}
调用的是 ci, err := db.connector.Connect(ctx)
这里就对应了go-sql-driver里的实现
func (c *connector) Connect(ctx context.Context) (driver.Conn, error)
3,查询和执行
//查询
func (db *DB) query(ctx context.Context, query string, args []interface{}, strategy connReuseStrategy) (*Rows, error) {
dc, err := db.conn(ctx, strategy)
return db.queryDC(ctx, nil, dc, dc.releaseConn, query, args)
}
func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {
nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
}
database/sql/ctxutil.go
func ctxDriverQuery(ctx context.Context, queryerCtx driver.QueryerContext, queryer driver.Queryer, query string, nvdargs []driver.NamedValue) (driver.Rows, error) {
return queryerCtx.QueryContext(ctx, query, nvdargs)
return queryer.Query(query, dargs)
}
//执行
func (db *DB) exec(ctx context.Context, query string, args []interface{}, strategy connReuseStrategy) (Result, error) {
dc, err := db.conn(ctx, strategy)
return db.execDC(ctx, dc, dc.releaseConn, query, args)
}
也分别在go-sql-driver里有对应的实现
推荐阅读