Quantcast
Channel: CodeSection,代码区,数据库(综合) - CodeSec
Viewing all articles
Browse latest Browse all 6262

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

$
0
0
#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()

Viewing all articles
Browse latest Browse all 6262

Trending Articles