第三方库 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的主要方法:
ExecQueryQueryRowClose
用法与DB类似
Rows的主要方法:
Cloumns//返回[]string,column namesScanNextClose
二、源码分析
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() errorBegin() (Tx, error)}
连接里面有三个方法,其中Prepare返回的是一个interface stmt
type Stmt interface {Close() errorNumInput() intExec(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() []stringClose() errorNext(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 *mysqlConnid uint32paramCount int}func (stmt *mysqlStmt) Close() errorfunc (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():returncase <-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.gofunc 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里有对应的实现
推荐阅读
