翔云

Just try, don't shy. 最新文章请点击
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

golang go-sql-driver/mysql基本原理

Posted on 2020-11-01 22:59  翔云123456  阅读(987)  评论(0编辑  收藏  举报

上篇文章关于golang database_sql 包讲述了 database/sql 的整体设计框架。

本文简要介绍go-sql-driver/mysql的调用关系,是如何与database/sql关联起来的,包括从驱动注册到具体查询,每个步骤的底层调用。

我们查询MySQL的大体代码demo如下:

package main
import (
        "database/sql"
        "log"
        _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB
var dataBase = "root:Aa123456@tcp(127.0.0.1:3306)/?loc=Local&parseTime=true"

func main() {
        var err error
        DB, err = sql.Open("mysql", dataBase)
        if err != nil {
                log.Fatalln("open db fail:", err)
        }

        var connection_id int
        err := DB.QueryRow("select CONNECTION_ID()").Scan(&connection_id)
        if err != nil {
                log.Println("query connection id failed:", err)
                return
        }

        log.Println("connection id:", connection_id)
}

从上面的代码可以看到,
要连接MySQL,首先是注册MySQL驱动 go-sql-driver/mysql

1.驱动注册

驱动注册代码:

func init() {
    	sql.Register("mysql", &MySQLDriver{})
}

位置:github.com/go-sql-driver/mysql/driver.go

上面的demo中,却没有看到这个操作。
那是什么时候做的呢?

答案是import 的时候:

import (
	_ "github.com/go-sql-driver/mysql"
)

接着看下Register 函数的实现:

// Register makes a database driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver driver.Driver) {
	driversMu.Lock()
	defer driversMu.Unlock()
	if driver == nil {
		panic("sql: Register driver is nil")
	}
	if _, dup := drivers[name]; dup {
		panic("sql: Register called twice for driver " + name)
	}
	drivers[name] = driver
}

其中,drivers是一个map,定义为:

var (
	drivers   = make(map[string]driver.Driver)
)

也就是说,
Register只是将MySQLDriver放到一个map中,key为mysql

Register 的第二个参数类型是driver.Driver,是一个接口类型。

接口定义如下:

// Driver is the interface that must be implemented by a database
// driver.
//
// Database drivers may implement DriverContext for access
// to contexts and to parse the name only once for a pool of connections,
// instead of once per connection.
type Driver interface {
	// Open returns a new connection to the database.
	// The name is a string in a driver-specific format.
	//
	// Open may return a cached connection (one previously
	// closed), but doing so is unnecessary; the sql package
	// maintains a pool of idle connections for efficient re-use.
	//
	// The returned connection is only used by one goroutine at a
	// time.
	Open(name string) (Conn, error)
}

接口中定义了一个Open函数,因此MySQLDriver必须实现这个函数。

go-sql-driver/mysql中已在文件github.com/go-sql-driver/mysql/driver.go中实现了该函数:

// Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formatted
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
    ... ...
	// New mysqlConn
	mc := &mysqlConn{
		maxAllowedPacket: maxPacketSize,
		maxWriteSize:     maxPacketSize - 1,
		closech:          make(chan struct{}),
	}
    ... ...
    return mc, nil
}

2.打开一个database

DB, err = sql.Open("mysql", dataBase)

第一个参数是driver名称,第二个参数是dsn(DataSourceName),指数据库地址。

该函数从driversmap中找到mysql的driver,将其作为参数初始化DB结构体,并返回。

DB接口定义:

type DB struct {
	connector driver.Connector
	... ...
}

type dsnConnector struct {
	dsn    string
	driver driver.Driver
}

func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) {
	return t.driver.Open(t.dsn)
}

func (t dsnConnector) Driver() driver.Driver {
	return t.driver
}

3.查询

var connection_id int
err := DB.QueryRow("select CONNECTION_ID()").Scan(&connection_id)
if err != nil {
        log.Println("query connection id failed:", err)
        return
}

log.Println("connection id:", connection_id)

底层调用关系是这样的:

func (db *DB) QueryRow()
  -->db.QueryRowContext() 
    -->db.QueryContext()
        --> db.conn() --> 最后调用的是MySQLDriver.Open()
        --> db.queryDC() --> 最后调用的是mysqlConn.Query()

具体调用关系如下图所示。
在这里插入图片描述

其他的查询也是类似的调用过程。

MySQLDriver代码关系如下图所示。
在这里插入图片描述

4.参考

go-sql-driver /mysql

golang package sql