第三方庫 go-sql-driver 源碼淺析
一、go-sql-driver使用過程
1、建立連接
首先是Open,
db, err := sql.Open(“mysql”, “user:password@/dbname”)
db 是一個(gè)*sql.DB類型的指針,在后面的操作中,都要用到db
open之后,并沒有與數(shù)據(jù)庫建立實(shí)際的連接,與數(shù)據(jù)庫建立實(shí)際的連接是通過Ping方法完成。此外,db應(yīng)該在整個(gè)程序的生命周期中存在,也就是說,程序一啟動(dòng),就通過Open獲得db,直到程序結(jié)束,再Close db,而不是經(jīng)常Open/Close。
err = db.Ping()
2、基本用法
DB的主要方法有:
Query 執(zhí)行數(shù)據(jù)庫的Query操作,例如一個(gè)Select語句,返回*Rows
QueryRow 執(zhí)行數(shù)據(jù)庫至多返回1行的Query操作,返回*Row
PrePare 準(zhǔn)備一個(gè)數(shù)據(jù)庫query操作,返回一個(gè)*Stmt,用于后續(xù)query或執(zhí)行。這個(gè)Stmt可以被多次執(zhí)行,或者并發(fā)執(zhí)行
Exec 執(zhí)行數(shù)不返回任何rows的據(jù)庫語句,例如delete操作
Stmt的主要方法:
ExecQueryQueryRowClose
用法與DB類似
Rows的主要方法:
Cloumns//返回[]string,column namesScanNextClose
二、源碼分析
1,初始化
golang的源碼包里database/sql只定義了連接池和常用接口、數(shù)據(jù)類型
具體到mysql 的協(xié)議實(shí)現(xiàn)在
github.com/go-sql-driver/mysql因此我們需要在使用的時(shí)候這樣導(dǎo)入依賴
import ("database/sql"_ "github.com/go-sql-driver/mysql")
這個(gè)import做了什么呢
_ "github.com/go-sql-driver/mysql"
我們可以在driver.go里看到下面這個(gè)函數(shù)
func init() {sql.Register("mysql", &MySQLDriver{})}
向sql的驅(qū)動(dòng)里注入了mysql 的實(shí)現(xiàn)。
先看下golang 源碼中驅(qū)動(dòng)相關(guān)的代碼,定義在這個(gè)文件中:src/database/sql/sql.go
var drivers = make(map[string]driver.Driver)func Register(name string, driver driver.Driver) {drivers[name] = driver}
注冊的過程就是將驅(qū)動(dòng)存入這個(gè)map
它的value是一個(gè)interface,定義在src/database/driver/driver.go這個(gè)文件中
type Driver interface {Open(name string) (Conn, error)}
只有一個(gè)方法,Opne,返回是一個(gè)表示連接的interface
type Conn interface {Prepare(query string) (Stmt, error)Close() errorBegin() (Tx, error)}
連接里面有三個(gè)方法,其中Prepare返回的是一個(gè)interface stmt
type Stmt interface {Close() errorNumInput() intExec(args []Value) (Result, error)Query(args []Value) (Rows, error)}
在stmt中我們常用到的是兩個(gè)接口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實(shí)現(xiàn)了type Driver interface
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {cfg, err := ParseDSN(dsn)return c.Connect(context.Background())}
而在connection.go,里實(shí)現(xiàn)了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里實(shí)現(xiàn)了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方法里調(diào)用的正是這個(gè)方法c.Connect(context.Background())
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {cfg, err := ParseDSN(dsn)return c.Connect(context.Background())}
上面就完成了整個(gè)初始化的過程,下面我們來看看連接的過程
2,連接
連接的時(shí)候我們調(diào)用的是golang源碼中的Open函數(shù)
func Open(driverName, dataSourceName string) (*DB, error) {//獲取驅(qū)動(dòng)driveri, ok := drivers[driverName]connector, err := driverCtx.OpenConnector(dataSourceName)//連接數(shù)據(jù)庫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()}
調(diào)用的是 ci, err := db.connector.Connect(ctx)
這里就對應(yīng)了go-sql-driver里的實(shí)現(xiàn)
func (c *connector) Connect(ctx context.Context) (driver.Conn, error)3,查詢和執(zhí)行
//查詢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)}
//執(zhí)行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里有對應(yīng)的實(shí)現(xiàn)
推薦閱讀
