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 裏有對應的實現

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/V4iNRuHa4vZFeHZLTLabTA