golang sql 解析

sql Pool

数据结构介绍

  • DB

DB 是一个数据库句柄, 包含了零个或多个基础连接的池. 对于多个goroutine并发使用是安全的.

sql package 自动 createrelease 连接;它还维护空闲连接的空闲池.

如果数据库具有 连接状态 的概念, 则可以在事务(Tx)或连接(Conn)中可靠地观察到这种状态.

调用 DB.Begin() 之后, 返回的 Tx 将绑定到单个连接. 一旦在事务上调用了 CommitRollback, 该事务的连接将返回到
DB的空闲连接池.

池大小可以通过 SetMaxIdleConns 控制.

type DB struct {
	// 仅限原子访问. 放置在首部, 是为了防止在32位平台上出现未对齐问题. 类型为time.Duration.
	waitDuration int64 // 等待新连接的总时间

	connector driver.Connector
	
	// numClosed是一个原子计数器, 表示关闭的连接总数. 
	// 在清除已关闭连接之前(Stmt.css方法中), Stmt.openStmt会对其进行检查.
	numClosed uint64

	mu           sync.Mutex // protects following fields
	freeConn     []*driverConn
	connRequests map[uint64]chan connRequest
	nextRequest  uint64 // 用于 connRequests 当中
	numOpen      int    // 已经连接的和正在连接的总数 
	
	// Used to signal the need for new connections
	// a goroutine running connectionOpener() reads on this chan and
	// maybeOpenNewConnections sends on the chan (one send per needed connection)
	// It is closed during db.Close(). The close tells the connectionOpener
	// goroutine to exit.
	openerCh          chan struct{}
	resetterCh        chan *driverConn
	closed            bool
	dep               map[finalCloser]depSet
	lastPut           map[*driverConn]string // debug 日志
	maxIdle           int                    // zero means defaultMaxIdleConns; negative means 0
	maxOpen           int                    // <= 0 means unlimited
	maxLifetime       time.Duration          // connection 最大重用的时间
	cleanerCh         chan struct{}          // 清理信号
	
	waitCount         int64 // 处于等待中的connections的数量
	maxIdleClosed     int64 // 由于idle而关闭的connections的数量
	maxLifetimeClosed int64 // 由于最大重用时间限制而关闭connections的数量

	stop func() // stop cancels the connection opener and the session resetter.
}
  • driverConn

driverConn 使用 mutex 包装 driver.Conn, 目的是为了 hold 住所有对 Conncalls (
包括对通过该 Conn 返回的接口的任何调用, 例如Tx, Stmt, Result, Row的调用)

type driverConn struct {
	db        *DB
	createdAt time.Time

	sync.Mutex  // guards following
	ci          driver.Conn
	closed      bool
	finalClosed bool // ci.Close has been called
	openStmt    map[*driverStmt]bool
	lastErr     error // lastError captures the result of the session resetter.

	// guarded by db.mu
	inUse      bool
	onPut      []func() // code (with db.mu held) run when conn is next returned
	dbmuClosed bool     // same as closed, but guarded by db.mu, for removeClosedStmtLocked
}

method

  • connectionOpener, connectionResetter (job)
// 运行在单独的goroutine当中, 用于创建新的connections
func (db *DB) connectionOpener(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			return
		case <-db.openerCh:
			db.openNewConnection(ctx)
		}
	}
}

// 运行在单独的goroutine当中, rest connections(异步方式)
func (db *DB) connectionResetter(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			close(db.resetterCh)
			for dc := range db.resetterCh {
				dc.Unlock()
			}
			return
		case dc := <-db.resetterCh:
			dc.resetSession(ctx)
		}
	}
}
  • openNewConnection

create new connection

// 创建新的 connection 
func (db *DB) openNewConnection(ctx context.Context) {
	// 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.connector.Connect(ctx)
	db.mu.Lock()
	defer db.mu.Unlock()
	
	// db 已关闭
	if db.closed {
		if err == nil {
			ci.Close()
		}
		db.numOpen-- // opened and opening 的数量
		return
	}
	
	// open failed
	if err != nil {
		db.numOpen--
		db.putConnDBLocked(nil, err) // 释放 connRequest 
		db.maybeOpenNewConnections() // 重新打开的操作
		return
	}
	
	// wrap
	dc := &driverConn{
		db:        db,
		createdAt: nowFunc(),
		ci:        ci,
	}
	
	// err为nil
	if db.putConnDBLocked(dc, err) {
		db.addDepLocked(dc, dc)  // 新创建的依赖会添加依赖
	} else {
		db.numOpen--
		ci.Close()
	}
}

调用的地方 4 处:

1.DB.openNewConnection() 连接创建失败了, 当然需要重新创建了
2.DB.conn() 连接创建失败了
3.DB.putConn() 连接放回到freeConn当中, 但是连接最新错误是ErrBadConn
4.driverConn.finalClose() 当前的连接已经关闭, 连接可能缺少了

该函数的调用导致补充新的连接.``

// 假设db.mu已锁定.
// 如果有 connRequests 并且尚未达到连接限制, 则告诉 `connectionOpener` 打开新的连接.
func (db *DB) maybeOpenNewConnections() {
	numRequests := len(db.connRequests) // 需要的连接数量
	if db.maxOpen > 0 {
		numCanOpen := db.maxOpen - db.numOpen
		if numRequests > numCanOpen {
			numRequests = numCanOpen
		}
	}
	
	// 打开 numRequests 个连接
	for numRequests > 0 {
		db.numOpen++ // optimistically
		numRequests--
		if db.closed {
			return
		}
		db.openerCh <- struct{}{} // 打开新的连接
	}
}
  • putConnDBLocked

put driverConn to connRequest or idle pool

调用的地方 2 处:

1.DB.putConn() 将连接放入 freeConn 的时候;
2.DB.openNewConnection() 的时候, 接收到创建的信号, 创建了新的连接, 当然需要给需要的地方

// 在 db.mu.Lock 的状况下调用
// true 表示当前的 driverConn 可以给 connRequest 或 可以进入 freeConn.
// flase 表示当前的 driverConn 没有用, 需要释放.
// 
// 逻辑如下: 
// 如果存在一个 connRequest, 则putConnDBLocked将满足connRequest; 
// 如果err == nil并且不超过空闲连接限制, 它将把 driverConn返回到freeConn列表.
// 
// err 与 dc 的条件限定:
// 如果err != nil, 则忽略dc的值.
// 如果err == nil, 则dc不能等于nil.
func (db *DB) putConnDBLocked(dc *driverConn, err error) bool {
	// 关闭
	if db.closed {
		return false
	}
	// 超出配置的连接的限制
	if db.maxOpen > 0 && db.numOpen > db.maxOpen {
		return false
	}
	
	if c := len(db.connRequests); c > 0 {
		// 存在连接请求, 连接重用
		var req chan connRequest
		var reqKey uint64
		for reqKey, req = range db.connRequests {
			break
		}
		delete(db.connRequests, reqKey) // Remove from pending requests.
		if err == nil {
			dc.inUse = true 
		}
		req <- connRequest{
			conn: dc,
			err:  err,
		}
		return true
	} else if err == nil && !db.closed {
	    // 不存在连接请求, 连接空闲
	    
	    // db.maxIdleConnsLocked() 配置最大是空闲数量
	    // db.freeConn, 空闲连接
		if db.maxIdleConnsLocked() > len(db.freeConn) {
			db.freeConn = append(db.freeConn, dc)
			db.startCleanerLocked() // 开启清理模式
			return true
		}
		
		// 超出空闲上限, 只能关闭了
		db.maxIdleClosed++
	}
	
	return false
}
  • putConn

put driverConn to freeConn pool

dc: *driverConn
err: current error
resetSession: reset driverConn Session

两个地方会调用:

1.DB.conn() 取消的时候,
2.driverConn 释放连接的时候 finalColse()

// adds a connection to the db's free pool.
func (db *DB) putConn(dc *driverConn, err error, resetSession bool) {
	db.mu.Lock()
	
	// 当前的 dc.inUse 为 true
	if !dc.inUse {
		if debugGetPut {
			fmt.Printf("putConn(%v) DUPLICATE was: %s\n\nPREVIOUS was: %s", dc, stack(), db.lastPut[dc])
		}
		panic("sql: connection returned that was never out")
	}
	if debugGetPut {
		db.lastPut[dc] = stack()
	}
	dc.inUse = false

  // callback
	for _, fn := range dc.onPut {
		fn()
	}
	dc.onPut = nil

	if err == driver.ErrBadConn {
		// Don't reuse bad connections.
		// Since the conn is considered bad and is being discarded, treat it as closed. 
		// Don't decrement the open count here, finalClose will take care of that.
		db.maybeOpenNewConnections()
		db.mu.Unlock()
		dc.Close()
		return
	}
	
	// Hook
	if putConnHook != nil {
		putConnHook(db, dc)
	}
	 
	if db.closed {
		// Connections do not need to be reset if they will be closed.
		// Prevents writing to resetterCh after the DB has closed.
		resetSession = false
	}
	if resetSession {
		if _, resetSession = dc.ci.(driver.SessionResetter); resetSession {
			// Lock the driverConn here so it isn't released until the connection is reset.
			// The lock must be taken before the connection is put into the pool to prevent it from 
			// being taken out before it is reset.
			dc.Lock()
		}
	}
	added := db.putConnDBLocked(dc, nil)
	db.mu.Unlock()

  // need to release driverConn
	if !added {
		if resetSession {
			dc.Unlock()
		}
		dc.Close()
		return
	}
	
	// driverConn 被重用 或者 放到了 freeConn 当中
	if !resetSession {
		return
	}
	
	// 发送信号重置 driverConn 的 Session
	select {
	default:
		// If the resetterCh is blocking then mark the connection as bad and continue on.
		dc.lastErr = driver.ErrBadConn
		dc.Unlock()
	case db.resetterCh <- dc:
	}
}
  • startCleanerLocked
// db.mu.Lock() 下开启清理模式:
func (db *DB) startCleanerLocked() {
    // 条件: 存在最大reuse时间, 存在最大连接限制, 并且当前的 cleanerCh 为 nil
	if db.maxLifetime > 0 && db.numOpen > 0 && db.cleanerCh == nil {
		db.cleanerCh = make(chan struct{}, 1)
		go db.connectionCleaner(db.maxLifetime)
	}
}

// 连接清理工作Job, d 是清理间隔
func (db *DB) connectionCleaner(d time.Duration) {
	const minInterval = time.Second
  
  // 清理的时间最小是1s
	if d < minInterval {
		d = minInterval
	}
	t := time.NewTimer(d)

	for {
		select {
		case <-t.C:
		case <-db.cleanerCh: // maxLifetime was changed or db was closed.
		}

		db.mu.Lock()
		d = db.maxLifetime // 这里 d 发生了改变, 也就是说maxLifetime可以在1s以下
		// db closed, or connections no limit, or maxLifetime lte 0
		if db.closed || db.numOpen == 0 || d <= 0 {
			db.cleanerCh = nil
			db.mu.Unlock()
			return
		}
    
		expiredSince := nowFunc().Add(-d) // 已经过期的时间点
		var closing []*driverConn
		// 从 freeConn 当中进行清理
		for i := 0; i < len(db.freeConn); i++ {
			c := db.freeConn[i]
			if c.createdAt.Before(expiredSince) {
				closing = append(closing, c) // 当前 i 加入到closeing队列
				last := len(db.freeConn) - 1 
				db.freeConn[i] = db.freeConn[last] // 最后一位复制到当前的位置
				db.freeConn[last] = nil 
				db.freeConn = db.freeConn[:last] // 更新freConn数组
				i--
			}
		}
		db.maxLifetimeClosed += int64(len(closing))
		db.mu.Unlock()

    // 清理
		for _, c := range closing {
			c.Close()
		}
    
    // 重新设置清理周期, 最少1s
		if d < minInterval {
			d = minInterval
		}
		t.Reset(d)
	}
}
  • conn

conn, 获取数据库连接. newly or cached *driverConn

ctx: context.Context
strategy: 更新策略, alwaysNewConn(0), cachedOrNewConn(1)

func (db *DB) conn(ctx context.Context, strategy connReuseStrategy) (*driverConn, error) {
	db.mu.Lock()
	if db.closed {
		db.mu.Unlock()
		return nil, errDBClosed
	}
	// Check if the context is expired.
	select {
	default:
	case <-ctx.Done():
		db.mu.Unlock()
		return nil, ctx.Err()
	}
	lifetime := db.maxLifetime
	
	//  使用Cached
	numFree := len(db.freeConn) // free数量
	if strategy == cachedOrNewConn && numFree > 0 {
		conn := db.freeConn[0]
		copy(db.freeConn, db.freeConn[1:]) // 更新freeConn
		db.freeConn = db.freeConn[:numFree-1]
		conn.inUse = true
		db.mu.Unlock()
		// 过期
		if conn.expired(lifetime) {
			conn.Close()
			return nil, driver.ErrBadConn
		}
		
		// 锁定当前的conn, 读取lastErr(最新的error), 确定是否重置该 conn
		conn.Lock()
		err := conn.lastErr
		conn.Unlock()
		if err == driver.ErrBadConn {
			conn.Close()
			return nil, driver.ErrBadConn
		}
		return conn, nil
	}
  
  // 限制条件判别, 当前已经达到最大连接数量. 需要connRequest进行等待, 可能是创建, 也可能是缓存当中获取
	if db.maxOpen > 0 && db.numOpen >= db.maxOpen {
		// Make the connRequest channel. It's buffered so that the
		// connectionOpener doesn't block while waiting for the req to be read.
		req := make(chan connRequest, 1)
		reqKey := db.nextRequestKeyLocked() // 获取reqKey, Lock 情况下获取
		db.connRequests[reqKey] = req
		db.mu.Unlock()

		// Timeout the connection request with the context.
		select {
		case <-ctx.Done():
			// Remove the connection request and ensure no value has been sent
			// on it after removing.
			db.mu.Lock()
			delete(db.connRequests, reqKey)
			db.mu.Unlock()
			
			// 请求已经获取到了 conn, 但是此时已经取消了任务
			select {
			default:
			case ret, ok := <-req:
				if ok && ret.conn != nil {
					db.putConn(ret.conn, ret.err, false) // 任务取消, 需要释放连接到 free pool
				}
			}
			return nil, ctx.Err()
			
		case ret, ok := <-req:
			if !ok {
				return nil, errDBClosed
			}
			
			// 过期了
			if ret.err == nil && ret.conn.expired(lifetime) {
				ret.conn.Close()
				return nil, driver.ErrBadConn
			}
			
			// 不存在的conn
			if ret.conn == nil {
				return nil, ret.err
			}
			
			// 读取 lastErr, 进行判别
			ret.conn.Lock()
			err := ret.conn.lastErr
			ret.conn.Unlock()
			if err == driver.ErrBadConn {
				ret.conn.Close()
				return nil, driver.ErrBadConn
			}
			
			return ret.conn, ret.err
		}
	}
 
  // 创建新的请求
	db.numOpen++ // optimistically
	db.mu.Unlock()
	ci, err := db.connector.Connect(ctx)
	if err != nil {
		db.mu.Lock()
		db.numOpen-- // correct for earlier optimism
		db.maybeOpenNewConnections()
		db.mu.Unlock()
		return nil, err
	}
	
	// 获取到了
	db.mu.Lock()
	dc := &driverConn{
		db:        db,
		createdAt: nowFunc(),
		ci:        ci,
		inUse:     true,
	}
	db.addDepLocked(dc, dc) // 新创建的连接, 会添加依赖
	db.mu.Unlock()
	return dc, nil
}
  • Close

关闭数据库, 并 “阻止启动新查询”.

关闭, 然后等待在服务器上已经开始处理的所有查询完成.

// 关闭数据库, 并 "阻止启动新查询".
// 关闭, 然后等待在服务器上已经开始处理的所有查询完成.
//
// 关闭数据库很少, 因为该数据库句柄是长期存在的并且在许多goroutine之间共享.
func (db *DB) Close() error {
	db.mu.Lock()
	if db.closed { // Make DB.Close idempotent
		db.mu.Unlock()
		return nil
	}
	
	// 关闭清理任务
	if db.cleanerCh != nil {
		close(db.cleanerCh)
	}
	
	// 清理freeConn列表(添加回调函数), closed状态, 关闭所有的ConnRequests(申请连接请求)
	var err error
	fns := make([]func() error, 0, len(db.freeConn))
	for _, dc := range db.freeConn {
		fns = append(fns, dc.closeDBLocked())
	}
	db.freeConn = nil
	db.closed = true
	for _, req := range db.connRequests {
		close(req)
	}
	db.mu.Unlock()
	
	// 对于 freeConn 的每一个 driverConn 执行`解绑`操作. 减少引用的操作
	for _, fn := range fns {
		err1 := fn()
		if err1 != nil {
			err = err1
		}
	}
	
	// db 执行 context 的 cancel() 的退出函数
	db.stop()
	return err
}
func (dc *driverConn) closeDBLocked() func() error {
	dc.Lock()
	defer dc.Unlock()
	if dc.closed {
		return func() error { return errors.New("sql: duplicate driverConn close") }
	}
	dc.closed = true
	return dc.db.removeDepLocked(dc, dc)
}
  • Dep 相关的函数
// 依赖关联计数
type finalCloser interface {
	// 当所有的引用object的数量变为0, 会调用此函数. 当调用此函数的时候, (*DB).mu 不加锁
	finalClose() error
}
// addDep, 将 x 和 dep 进行依赖绑定. 一般都是 `self bind self`
func (db *DB) addDep(x finalCloser, dep interface{}) {
	db.mu.Lock()
	defer db.mu.Unlock()
	db.addDepLocked(x, dep)
}

func (db *DB) addDepLocked(x finalCloser, dep interface{}) {
	if db.dep == nil {
		db.dep = make(map[finalCloser]depSet)
	}
	xdep := db.dep[x]
	if xdep == nil {
		xdep = make(depSet)
		db.dep[x] = xdep
	}
	xdep[dep] = true
}

// removeDep notes that x no longer depends on dep.
// 如果 x 有 dependencies, 返回 nil
// 如果 x 没有任何的 dependencies, x.finalClose() 会被调用, 返回调用的结果
func (db *DB) removeDep(x finalCloser, dep interface{}) error {
	db.mu.Lock()
	fn := db.removeDepLocked(x, dep)
	db.mu.Unlock()
	return fn()
}

func (db *DB) removeDepLocked(x finalCloser, dep interface{}) func() error {
	xdep, ok := db.dep[x]
	if !ok {
		panic(fmt.Sprintf("unpaired removeDep: no deps for %T", x))
	}

	l0 := len(xdep)
	delete(xdep, dep)

	switch len(xdep) {
	case l0:
		// Nothing removed. Shouldn't happen.
		panic(fmt.Sprintf("unpaired removeDep: no %T dep on %T", dep, x))
	case 0:
		// No more dependencies.
		delete(db.dep, x)
		return x.finalClose
	default:
		// Dependencies remain.
		return func() error { return nil }
	}
}
  • driverConn
func (dc *driverConn) releaseConn(err error) {
	dc.db.putConn(dc, err, true) // 将 dc 放入到 free pool当中
}

// the dc.db's Mutex is held.
func (dc *driverConn) closeDBLocked() func() error {
	dc.Lock()
	defer dc.Unlock()
	if dc.closed {
		return func() error { return errors.New("sql: duplicate driverConn close") }
	}
	dc.closed = true
	return dc.db.removeDepLocked(dc, dc) // 移除依赖
}

// Close 
func (dc *driverConn) Close() error {
	dc.Lock()
	if dc.closed {
		dc.Unlock()
		return errors.New("sql: duplicate driverConn close")
	}
	dc.closed = true
	dc.Unlock() // not defer; removeDep finalClose calls may need to lock

	// And now updates that require holding dc.mu.Lock.
	dc.db.mu.Lock()
	dc.dbmuClosed = true
	fn := dc.db.removeDepLocked(dc, dc) // 移除依赖
	dc.db.mu.Unlock()
	return fn() // 可能是执行 finalClose() 函数
}

// 当 driverConn 解除依赖, Closed 的函数
func (dc *driverConn) finalClose() error {
	var err error

	// Each *driverStmt has a lock to the dc. Copy the list out of the dc
	// before calling close on each stmt.
	var openStmt []*driverStmt
	withLock(dc, func() {
		openStmt = make([]*driverStmt, 0, len(dc.openStmt))
		for ds := range dc.openStmt {
			openStmt = append(openStmt, ds)
		}
		dc.openStmt = nil
	})
	for _, ds := range openStmt {
		ds.Close()
	}
	withLock(dc, func() {
		dc.finalClosed = true
		err = dc.ci.Close()
		dc.ci = nil
	})

	dc.db.mu.Lock()
	dc.db.numOpen--
	dc.db.maybeOpenNewConnections()
	dc.db.mu.Unlock()

	atomic.AddUint64(&dc.db.numClosed, 1)
	return err
}
  • Query, Ping, Exec, Tx

XXX -> XXXContext -> xxx -> xxxDC

XXXContext, 使用 Context 进行包装, 可以随时取消

xxx, 获取 driverConn 连接

xxxDC, 执行具体的操作, 会释放连接(error)或者由返回的结果释放连接

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++ {
		rows, err = db.query(ctx, query, args, cachedOrNewConn)
		if err != driver.ErrBadConn {
			break
		}
	}
	if err == driver.ErrBadConn {
		return db.query(ctx, query, args, alwaysNewConn)
	}
	return rows, err
}

func (db *DB) query(ctx context.Context, query string, args []interface{}, strategy connReuseStrategy) (*Rows, error) {
	dc, err := db.conn(ctx, strategy)
	if err != nil {
		return nil, err
	}

	return db.queryDC(ctx, nil, dc, dc.releaseConn, query, args)
}

func (db *DB) queryDC(ctx, txctx context.Context, dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {
	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() {
			nvdargs, err = driverArgsConnLocked(dc.ci, nil, args)
			if err != nil {
				return
			}
			rowsi, err = ctxDriverQuery(ctx, queryerCtx, queryer, query, nvdargs)
		})
		if err != driver.ErrSkip {
			if err != nil {
				releaseConn(err) // release 1
				return nil, err
			}
			// Note: ownership of dc passes to the *Rows, to be freed
			// with releaseConn.
			rows := &Rows{
				dc:          dc,
				releaseConn: releaseConn, // release 2
				rowsi:       rowsi,
			}
			rows.initContextClose(ctx, txctx)
			return rows, nil
		}
	}

	var si driver.Stmt
	var err error
	withLock(dc, func() {
		si, err = ctxDriverPrepare(ctx, dc.ci, query)
	})
	if err != nil {
		releaseConn(err) // release 3
		return nil, err
	}

	ds := &driverStmt{Locker: dc, si: si}
	rowsi, err := rowsiFromStatement(ctx, dc.ci, ds, args...)
	if err != nil {
		ds.Close()
		releaseConn(err) // release 4
		return nil, err
	}

	// Note: ownership of ci passes to the *Rows, to be freed
	// with releaseConn.
	rows := &Rows{
		dc:          dc,
		releaseConn: releaseConn, // release 5
		rowsi:       rowsi,
		closeStmt:   ds,
	}
	rows.initContextClose(ctx, txctx)
	return rows, nil
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值