最近学习到go语言使用go-sql-driver/mysql驱动时,遇到一些问题,这里总结和分析下,看对gopher们有木有用。
首先,网上找的文章,大都是雷同的怎么怎么使用,但比较少涉及到具体的调用流程,我也是初学者,理解不一定透彻,如有问题,读者朋友们请不吝赐教。
网上的文章包括官方都是说先导入包:
import (
"database/sql"
_"github.com/go-sql-driver/mysql"
)
然后就开始各种api如何调用的例子了,但为何import包时,"github.com/go-sql-driver/mysql"加下划线空指示符呢?假如不加下划线,编译时会报imported and not used: "github.com/go-sql-driver/mysql"错误,那么不导入行不行?显然也不行,这样的导入方式的意义何在?这个驱动是如何被调用的呢?可能很多人没有注意或者现在发现这个问题也有点蒙圈了,别急,先让我们温习下:
1.go语言本身没有没有提供连接mysql的驱动,但是定义了标准接口供第三方开发驱动。而go-sql-driver/mysql是比较流行的一个,它实现了go定义的标准接口。所以我们使用标准接口,实际调用的是go-sql-driver/mysql驱动的实现。
2.我们知道,go语言的每个包都有一个或多个init函数,且该函数没有参数和返回值,也不能显示的调用,它用于执行初始化任务,也可用于在执行开始之前验证程序的正确性。
一个包的初始化顺序如下:
- 包级别的变量首先被初始化
- 接着init函数被调用。一个包可以有多个init函数(在一个或多个文件中),它们的调用顺序为编译器解析它们的顺序。
如果一个包导入了另一个包,被导入的包先初始化。一个包可能被包含多次,但是它只被初始化一次。
如果你想一个包的init函数被调用但不想在代码的其他地方引用它,那么可以使用下划线空指示符来处理。
好了,讲了这么多,跟我一开始说的问题有几毛钱关系?干货就在这:
先看一段代码吧:
package db
import (
"fmt"
"database/sql"
"time"
_"github.com/go-sql-driver/mysql"
"mycom.com/test/config"
)
var iShareDB *sql.DB
func InitDB() {
var err error
cfg := &config.GlobalConfig
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", cfg.DBUser, cfg.DBPasswd, cfg.DBAddr, cfg.DBName)
iShareDB, err = sql.Open("mysql", dataSourceName)
if err != nil {
panic(err)
}
iShareDB.SetConnMaxLifetime(time.Second*time.Duration(cfg.DBConnMaxLifeTime))
iShareDB.SetMaxOpenConns(cfg.DBMaxOpenConn) // 设置最大打开的连接数,默认值为0表示不限制
iShareDB.SetMaxIdleConns(cfg.DBMaxIdelConn) // 设置闲置的连接数
err = iShareDB.Ping()
if err != nil {
panic(err)
}
}
这里比较重要的就是sql.Open()的调用,先进入该函数看看:
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName] // drivers是个map,通过"mysql"键找到对应的驱动,那么驱动是如何注册进来的呢?
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
}
mysql驱动是如何注册进来的?查看go-sql-driver/mysql代码,在driver.go文件最后找到如下代码:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
为何import _"github.com/go-sql-driver/mysql" 现在是不是明了了。看sql.open()函数还启动了协程
go db.connectionOpener(),这里最终调用该函数:
// Open one new connection
func (db *DB) openNewConnection() {
// maybeOpenNewConnctions has already executed db.numOpen++ before it sent
// on db.openerCh. This function must execute db.numOpen-- if the
// connection fails or is closed before returning.
ci, err := db.driver.Open(db.dsn)
db.mu.Lock()
defer db.mu.Unlock()
// ...其他省略
}
函数第一行ci, err := db.driver.Open(db.dsn),调用的是go-sql-driver/mysql/driver.go的Open函数了,所以,是go-sql-driver/mysql也实现了该标准接口:
// Driver is the interface that must be implemented by a database
// driver.
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)
}
当然,驱动实现里有很多复杂的实现,我也在学习中。当初初次使用go-sql-driver/mysql的迷茫,现在是不是解决了?