go database/sql 源碼分析(四)sql.Stmt資料結構

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。#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() 




相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.