一般写法:
db,err := sql.Open("mysql","root:123456@tcp(127.0.0.1:3306)/husky?charset=utf8");
rows,err := db.Query("select * from test");
for rows.Next(){
var name string
rows.Columns()
err := rows.Scan(&name)
fmt.Println(name);
}
1.创建db
在执行Open()之前,在程序启动之后,会先执行mysql驱动中的init()方法,去注册驱动到go基础包的驱动管理中。而且这个init()方法是在调用Open()方法时才会提前加载。然后调用mysql驱动中的Open方法。
// github.com/go-sql-driver/mysql/driver.go
func init() {
// MySQLDriver实现了Driver接口
sql.Register("mysql", &MySQLDriver{})
}
// github.com/go-sql-driver/mysql/driver.go
// sql.Open实际上是调用的这个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)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
// OpenConnector实际上是在mysql驱动中实现的
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
// 主要任务就是dsn的解析
func (d MySQLDriver) OpenConnector(dsn string) (driver.Connector, error) {
cfg, err := ParseDSN(dsn)
if err != nil {
return nil, err
}
return &connector{
cfg: cfg,
}, nil
}
所以go中的sql.Open()方法实际上是不建立连接的,也不去做认证校验,如果要想真正的验证连接,可以执行db.Ping()方法。该方法也是真正的去创建连接,但只不过发送的ping的数据包,没有执行实际的查询语句。在使用完连接也会调用putConn()方法放回连接池。
2.执行查询
实际上调用的时QueryContext()方法,参数需要ctx,这个是帮助后续执行完查询或连接异常时释放连接用的。query即使sql语句,如果sql要写成“select * from table where name = ?”,则args要添加参数。
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
var rows *Rows
var err error
for i := 0; i < maxBadConnRetries; i++ {
// 执行db的query方法,使用cachedOrNewConn模式,会重试两次,maxBadConnRetries默认2
rows, err = db.query(ctx, query, args, cachedOrNewConn)
if err != driver.ErrBadConn {
break
}
}
if err == driver.ErrBadConn {
// 如果上面的失败,会再次尝试使用alwaysNewConn模式
return db.query(ctx, query, args, alwaysNewConn)
}
return rows, err
}
query方法实际包含两部分,一个是根据策略去生成或获取连接,另一个是执行真正的查询。
func (db *DB) query(ctx context.Context, query string, args []interface{}, strategy connReuseStrategy) (*Rows, error) {
// 实际是调用了db.conn方法,传入context和策略(cachedOrNewConn or alwaysNewConn)
// 该方法返回的是一个新建的或缓存的driverConn
// 对于mysql来说,是mysql驱动来实现的
// 该方法主要是:
// 实例化mysqlConn,
// dail()测试Server连通性
// 设置TCP连接的Keepalives
// 启动startWatcher(一个goroutine)监控ctx
// 进行mysql连接认证
dc, err := db.conn(ctx, strategy)
if err != nil {
return nil, err
}
return db.queryDC(ctx, nil, dc, dc.releaseConn, query, args)
}
queryDC()是真正的执行查询的方法,
func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {
// 目前官方只建议使用带ctx的接口实现
queryerCtx, ok := dc.ci.(driver.QueryerContext)
var queryer driver.Queryer
if !ok {
queryer, ok = dc.ci.(driver.Queryer)
}
if ok {
var nvdargs []driver.NamedValue
var rowsi driver.Rows
var err error
withLock(dc, func() {
// 做了一个类型转换,返回[]driver.NamedValue供ctxDriverQuery()方法使用
nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
if err != nil {
return
}
// 执行真正查询,实际会调用mysql驱动中的QueryContext()方法,
// QueryContext()方法中会真正的将sql和参数转换成符合mysql协议的包与mysql交互,
// 完成最后的查询,返回driver.Rows类型的查询结果
rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
})
if err != driver.ErrSkip {
if err != nil {
releaseConn(err)
return nil, err
}
// 下面是为了将dc的释放依赖于rows,在rows遍历完时执行close回收或释放连接
rows := &Rows{
dc: dc,
releaseConn: releaseConn,
rowsi: rowsi,
}
rows.initContextClose(ctx, txctx)
return rows, nil
}
}
var si driver.Stmt
var err error
withLock(dc, func() {
// statement预编译
si, err = ctxDriverPrepare(ctx, dc.ci, query)
})
if err != nil {
releaseConn(err)
return nil, err
}
ds := &driverStmt{Locker: dc, si: si}
// 利用driverStmt执行查询
rowsi, err := rowsiFromStatement(ctx, dc.ci, ds, args...)
if err != nil {
ds.Close()
releaseConn(err)
return nil, err
}
// 下面是为了将dc的释放依赖于rows,在rows遍历完时执行close回收或释放连接
rows := &Rows{
dc: dc,
releaseConn: releaseConn,
rowsi: rowsi,
closeStmt: ds,
}
rows.initContextClose(ctx, txctx)
return rows, nil
}
这里注意rows.initContextClose()方法,是等待rows的Next()方法遍历完成才会close掉这个rows,进而回收或释放连接,所以如果没有遍历完rows,连接是不会被收回的,再有其他请求需要获取连接就会新创建连接,在新链接用完回收时,会判断当前连接数是否大于最大空闲连接数,如果大于就会被close掉,如果有大量请求触发会造成性能问题。