第三方库 go-sql-driver 源码浅析

共 5824字,需浏览 12分钟

 ·

2021-07-27 06:50

一、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() 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中我们常用到的是两个接口ExecQuery,分别返回了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里有对应的实现



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。


浏览 62
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报