Go数据库查询底层流程

一般写法:

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掉,如果有大量请求触发会造成性能问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只努力的微服务

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值