go database/sql 源码分析(四)sql.Stmt数据结构

来源:互联网 发布:java中aes加密算法 编辑:程序博客网 时间:2024/05/24 06:57
#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中,标记一下。

 3.将获取的driverConn实例和driver.Stmt实例初始化connStmt,然后加入css中


#为什么绕一个大圈子,而不把Stmt绑定死一个driver.Conn和一个driver.Stmt,

#原因是sql包的作者想把Stmt和具体的连接解耦,为什么要解耦,原因是想让Stmt可以长久的使用(而不是频繁的创建和销毁),但是又不想让其长久的占用一个连接,而导致连接数的暴增,以及增加连接回收的困难性,这样也会导致一个问题就是在过多的连接上创建driver.Stmt实例,这个控制不好容易导致mysql 服务端的问题(导致Prepared_stmt_count值暴增)

database/sql: Stmt的使用以及坑


#拿到 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)=>#生成Stmtfunc (db *DB) Prepare(query string) (*Stmt, error)=>#内部func (db *DB) prepare(query string, strategy connReuseStrategy) (*Stmt, error) =>#生成driverConnfunc (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初始化过程########################################################生成Stmtfunc (db *DB) Prepare(query string) (*Stmt, error)=>#内部调用func (db *DB) prepare(query string, strategy connReuseStrategy) (*Stmt, error) {#创建driverConndc, err := db.conn(strategy)#创建driver.Stmtsi, 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) { #拿到一个连接*driverConndc, 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, nil1457         }1458     }#如果新拿到的连接没有缓存Stmt对应的sql的driver.Stmt数据结构#重新生成driver.Stmtsi, 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() 




0 0
原创粉丝点击