這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。#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()