go-sql-driver/mysql 源码分析--连接数据库

b365ea0d50f0a78428fc.jpeg

在使用go连"接数据库的时候,官方只是给我们提供了一个数据库连接的接口协议database/sql,并没有去实现这个接口,所以需要我们的来实现。

但是由我们自己来实现的database/sql这个协议,难度是相当大的。一般都是使用第三方已经写好的数据库驱动, go-sql-driver/mysql 是由国外大牛写的,也是目前使用比较广的mysql数据库驱动。

本着学习的想法,和大家分享一下本人在看go-sql-driver/mysql源代码的时候一些心得,如有不对的地方 希望和在文章结尾处留言,以免误导后面查看这篇文章的朋友。


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

db, err := sql.Open("mysql", "user:password@/dbname")


首先引入  _ "github.com/go-sql-driver/mysql"。这里要注意一下,我们在导入包的时候,在包前面使用 _ 符号,表示我们不需要使用这个包下面的函数,仅仅是是希望它执行init()函数而已。

根据上面说法,所以我们在driver.go#107行找到了init()这个函数。

func init() {
  // 代码很简单,就是调用database/sql这个包下面的Register()
  // 将MySQLDriver{}这个结构体注册到sql.drivers这个私有变量中去
  sql.Register("mysql", &MySQLDriver{})
}

打开database/sql包下面的sql.go文件

func Register(name string, driver driver.Driver) {
        // 因为map不是线程安全的,所以需要加锁
	driversMu.Lock()
	defer driversMu.Unlock()
	if driver == nil {
		panic("sql: Register driver is nil")
	}
         // 驱动名不能重复,有些时候我们需要从多个数据库源获取数据库,就是依靠name来的区分不同的数据源
	if _, dup := drivers[name]; dup {
		panic("sql: Register called twice for driver " + name)
	}
	drivers[name] = driver
}

把mysql驱动注册好后,调用sql.Open方法连接数据库

// 这里mysql字符串就是刚刚调用注册的时候填写的name值
db, err := sql.Open("mysql", "user:password@/dbname")

// 如果需要多个数据源,再调用注册即可sql.Register("mysql_db1", &MySQLDriver{})
// 在获取对应也换成注册的name值
// db, err := sql.Open("mysql_db1", "user:password@/dbname")

查看sql.Open这个函数

func Open(driverName, dataSourceName string) (*DB, error) {
        
	driversMu.RLock()
	driveri, ok := drivers[driverName]
	driversMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
	}
	db := &DB{
		driver:       driveri,
		dsn:          dataSourceName,
		openerCh:     make(chan struct{}, connectionRequestQueueSize),
		lastPut:      make(map[*driverConn]string),
		connRequests: make(map[uint64]chan connRequest),
	}
        // 开启协程连接
	go db.connectionOpener()
	return db, nil
}

// 此处省去N行代码

// 当请求的时候就打开一个新的连接
func (db *DB) connectionOpener() {
	for range db.openerCh {
		db.openNewConnection()
	}
}

// 此处省去N行代码

// 打开一个新的连接
func (db *DB) openNewConnection() {
        // 这个时候才真正调用go-sql-driver/mysql下面的Open() 方法连接数据库
	ci, err := db.driver.Open(db.dsn)
        // 省去N行代码
}

接着跳转到go-sql-driver/mysql 包下面的driver.go里面的Open()方法连接数据

func (d *MySQLDriver) Open(dsn string) (driver.Conn, error) {
	var err error
        
	// 初始化一个新的Mysql连接 
	mc := &mysqlConn{
		maxPacketAllowed: maxPacketSize,
		maxWriteSize:     maxPacketSize - 1,
	}
        // 解析连接参数
	mc.cfg, err = parseDSN(dsn)
	if err != nil {
		return nil, err
	}


	nd := net.Dialer{Timeout: mc.cfg.timeout}
       // 调用net包里面的Dialer方法连接的数据库
	mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr)
	if err != nil {
		return nil, err
	}
	mc.buf = newBuffer(mc.netConn)
    
        // 获取mysql返回的握手包消息,稍后认证的时候需要用到
	cipher, err := mc.readInitPacket()
	if err != nil {
		mc.Close()
		return nil, err
	}
        
	// 发送认证密钥到mysql服务器
        // 下面我们会分析一下mysql是如何进行认证的
	if err = mc.writeAuthPacket(cipher); err != nil {
		mc.Close()
		return nil, err
	}

	// 判断是否的认证成功
	err = mc.readResultOK()
	if err != nil {
		// Retry with old authentication method, if allowed
                // 连接失败,如果允许使用老版本的认证方式,将再次重新认证一次
		if mc.cfg.allowOldPasswords && err == errOldPassword {
			if err = mc.writeOldAuthPacket(cipher); err != nil {
				mc.Close()
				return nil, err
			}
			if err = mc.readResultOK(); err != nil {
				mc.Close()
				return nil, err
			}
		} else {
			mc.Close()
			return nil, err
		}

	}

	// 获取mysql最大支持的数据包大小,如果数据包超出最大限制,默认会采用分包的方式
	maxap, err := mc.getSystemVar("max_allowed_packet")
	if err != nil {
		mc.Close()
		return nil, err
	}
	mc.maxPacketAllowed = stringToInt(maxap) - 1
	if mc.maxPacketAllowed < maxPacketSize {
		mc.maxWriteSize = mc.maxPacketAllowed
	}

	// Handle DSN Params
	err = mc.handleParams()
	if err != nil {
		mc.Close()
		return nil, err
	}
        // 连接成功
	return mc, nil
} 
0条评论