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

hittata · · 1702 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

#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)
=>
#生成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() 





有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:hittata

查看原文:go database/sql 源码分析(四)sql.Stmt数据结构

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1702 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传