#sql.Stmt是sql包暴露给程序调用者的可见实体,一般通过db.Open函数获得DB实例后的下一步就是调用func (db *DB) Prepare 方法的的Stmt
#其内部通过 css []connStmt 来绑定相关的连接和驱动层driver.Stmt
#其内部不是引用driverConn,而是引用一个css []connStmt
#sql包中有两个方式能够创建Stmt实例,一个是DB Prepare() 一个是Tx的Prepare(),二者是有区别
#Tx创建的Stmt通过Tx关联的driverConn绑定到固定的网络连接上
#DB创建的Stmt时初始化过程
1.会从连接池拿一个空闲连接,然后创建connStmt实例加到Stmt的css切片里
2.创建过程是调用DB的conn获取一个可用的driverConn实例,然后调用driverConn 的driver.Conn的Prepare()创建driver.Stmt实例,将该实例加到driverConn 的openStmt map中,标记一下。
#拿到 DB 创建的Stmt实例后,下次使用时就需要一个获取连接,重新绑定driver.Conn和一个driver.Stmt的过程。
#Stmt的method Exec,Query 内部都会调用func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.Stmt, err error)函数来拿到
#重新绑定driver.Conn和一个driver.Stmt实例,这个获取的过程异常曲折:
1.判断Stmt的状态是否关闭
2.判断Stmt是否是由Tx创建的,如果是,直接从Tx实例中取得driverConn和driver.Stmt返回
3.注意css中缓存的连接有可能因为各种原因关闭了,需要调用removeClosedStmtLocked()做一次清理
4.调用Stmt关联的DB实例s.db.conn(cachedOrNewConn) 获取一个连接*driverConn
5.判断Stmt css是否已经缓存里该连接,如果已经缓存则说明之前在css中已经缓存了driverConn实例和driver.Stmt,则可以直接拿来使用
6.如果Stmt css没有缓存该连接,说明该Stmt的sql语句之前没有绑定到到该连接上,需要重新绑定:通过driverConn实例和sql语句创建driver.Stmt实例,然后初始化connStmt实例,加入css中,并将
driverConn实例和driver.Stmt返还
#第一个连接创立过程
#其内部通过 css []connStmt 来绑定相关的连接和驱动层driver.Stmt
#其内部不是引用driverConn,而是引用一个css []connStmt
#sql包中有两个方式能够创建Stmt实例,一个是DB Prepare() 一个是Tx的Prepare(),二者是有区别
#Tx创建的Stmt通过Tx关联的driverConn绑定到固定的网络连接上
#DB创建的Stmt时初始化过程
1.会从连接池拿一个空闲连接,然后创建connStmt实例加到Stmt的css切片里
2.创建过程是调用DB的conn获取一个可用的driverConn实例,然后调用driverConn 的driver.Conn的Prepare()创建driver.Stmt实例,将该实例加到driverConn 的openStmt map中,标记一下。
3.将获取的driverConn实例和driver.Stmt实例初始化connStmt,然后加入css中
#原因是sql包的作者想把Stmt和具体的连接解耦,为什么要解耦,原因是想让Stmt可以长久的使用(而不是频繁的创建和销毁),但是又不想让其长久的占用一个连接,而导致连接数的暴增,以及增加连接回收的困难性,这样也会导致一个问题就是在过多的连接上创建driver.Stmt实例,这个控制不好容易导致mysql 服务端的问题(导致Prepared_stmt_count值暴增)
#拿到 DB 创建的Stmt实例后,下次使用时就需要一个获取连接,重新绑定driver.Conn和一个driver.Stmt的过程。
#Stmt的method Exec,Query 内部都会调用func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.Stmt, err error)函数来拿到
#重新绑定driver.Conn和一个driver.Stmt实例,这个获取的过程异常曲折:
1.判断Stmt的状态是否关闭
2.判断Stmt是否是由Tx创建的,如果是,直接从Tx实例中取得driverConn和driver.Stmt返回
3.注意css中缓存的连接有可能因为各种原因关闭了,需要调用removeClosedStmtLocked()做一次清理
4.调用Stmt关联的DB实例s.db.conn(cachedOrNewConn) 获取一个连接*driverConn
5.判断Stmt css是否已经缓存里该连接,如果已经缓存则说明之前在css中已经缓存了driverConn实例和driver.Stmt,则可以直接拿来使用
6.如果Stmt css没有缓存该连接,说明该Stmt的sql语句之前没有绑定到到该连接上,需要重新绑定:通过driverConn实例和sql语句创建driver.Stmt实例,然后初始化connStmt实例,加入css中,并将
driverConn实例和driver.Stmt返还
7.拿到driverConn实例和driver.Stmt后就可以直接调用驱动提供的method进行处理了。
#调用逻辑 #通过driverName获取driver,通过driver的Open()方法获得到DB的原始连接 func Open(driverName, dataSourceName string) (*DB, error) => #生成Stmt func (db *DB) Prepare(query string) (*Stmt, error) => #内部 func (db *DB) prepare(query string, strategy connReuseStrategy) (*Stmt, error) => #生成driverConn func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) => func (s *Stmt) Exec(args ...interface{}) (Result, error) func (s *Stmt) Query(args ...interface{}) (*Rows, error) =>#有可能使用Prepare时分配的连接,也有可能重新绑定连接 func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.Stmt, err error) => #注意这里仅仅是将Stmt实例的状态置为closed,对于TX会将连接关闭 func (s *Stmt) Close() error
#第一个连接创立过程
func Open(driverName, dataSourceName string) (*DB, error){ 475 db := &DB{ 476 driver: driveri, 477 dsn: dataSourceName, 478 openerCh: make(chan struct{}, connectionRequestQueueSize), 479 lastPut: make(map[*driverConn]string), 480 } go db.connectionOpener() } #此时并没有创建连接,只是初始化DB部分数据结构 #最简单的ping函数看看连接怎么建立 func (db *DB) Ping() error { 491 dc, err := db.conn(cachedOrNewConn) 492 if err != nil { 493 return err 494 } 495 db.putConn(dc, nil) } #conn并不直接创建连接先到db中寻找是否有空闲连接,没有则创建driverConn实例 func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error){ ... 708 db.numOpen++ // optimistically 709 db.mu.Unlock() 710 ci, err := db.driver.Open(db.dsn) 711 if err != nil { 712 db.mu.Lock() 713 db.numOpen-- // correct for earlier optimism 714 db.mu.Unlock() 715 return nil, err 716 } 717 db.mu.Lock() 718 dc := &driverConn{ 719 db: db, 720 ci: ci, 721 } 722 db.addDepLocked(dc, dc) 723 dc.inUse = true 724 db.mu.Unlock() 725 return dc, nil } #将使用完的driverConn实例放到db数据结构中 func (db *DB) putConn(dc *driverConn, err error) => func (db *DB) putConnDBLocked(dc *driverConn, err error) bool #另外sql包启动单独一个goroutine负责创建连接 go db.connectionOpener() 633 func (db *DB) connectionOpener() { 634 for range db.openerCh { 635 db.openNewConnection() 636 } 637 } 639 // Open one new connection 640 func (db *DB) openNewConnection() { 641 ci, err := db.driver.Open(db.dsn) 642 db.mu.Lock() 643 defer db.mu.Unlock() 644 if db.closed { 645 if err == nil { 646 ci.Close() 647 } 648 return 649 } 650 db.pendingOpens-- 651 if err != nil { 652 db.putConnDBLocked(nil, err) 653 return 654 } 655 dc := &driverConn{ 656 db: db, 657 ci: ci, 658 } 659 if db.putConnDBLocked(dc, err) { 660 db.addDepLocked(dc, dc) 661 db.numOpen++ 662 } else { 663 ci.Close() 664 } 665 }
####################################################### #Stmt初始化过程 ####################################################### #生成Stmt func (db *DB) Prepare(query string) (*Stmt, error) => #内部调用 func (db *DB) prepare(query string, strategy connReuseStrategy) (*Stmt, error) { #创建driverConn dc, err := db.conn(strategy) #创建driver.Stmt si, err := dc.prepareLocked(query) #driverConn和driver.Stmt被添加到css中 878 stmt := &Stmt{ 879 db: db, 880 query: query, 881 css: []connStmt{{dc, si}}, 882 lastNumClosed: atomic.LoadUint64(&db.numClosed), } 883 } #Stmt的初始化和执行是分开的,再次拿到Stmt运行时需要重新绑定,以Eexc()为例分析下 func (s *Stmt) Exec(args ...interface{}) (Result, error) { #最核心的connStmt()函数获取绑定Stmt的重新绑定 dc, releaseConn, si, err := s.connStmt() #func resultFromStatement(ds driverStmt, args ...interface{}) (Result, error) 调用驱动程序的driver.Stmt执行 res, err = resultFromStatement(driverStmt{dc, si}, args...) } func (s *Stmt) connStmt() (ci *driverConn, releaseConn func(error), si driver.Stmt, err error) { #拿到一个连接*driverConn dc, err := s.db.conn(cachedOrNewConn) #查询s.css中缓存的connStmt的*driverConn 是否和连接池拿到的连接是同一个,如果是同一个,则直接返回 #说明Stmt实例中的sql已经完成prepare初始化,可以直接使用了 1453 for _, v := range s.css { 1454 if v.dc == dc { 1455 s.mu.Unlock() 1456 return dc, dc.releaseConn, v.si, nil 1457 } 1458 } #如果新拿到的连接没有缓存Stmt对应的sql的driver.Stmt数据结构 #重新生成driver.Stmt si, err = dc.prepareLocked(s.query) #创建connStmt实例,将其插入到Stmt的css 切片中 cs := connStmt{dc, si} s.css = append(s.css, cs) } #如果连接池连接过多,Stmt执行时取到的连接是最初绑定的连接的概率会很低,这就会导致某个sql执行一次就要绑定一次,并且导致Stmt的 css绑定过多的连接。 #这个控制内部有个机制来去 func (s *Stmt) removeClosedStmtLocked()
有疑问加站长微信联系(非本文作者)