亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Golang > 正文

golang sql連接池的實現方法詳解

2020-04-01 18:53:26
字體:
來源:轉載
供稿:網友

前言

golang的”database/sql”是操作數據庫時常用的包,這個包定義了一些sql操作的接口,具體的實現還需要不同數據庫的實現,mysql比較優秀的一個驅動是:github.com/go-sql-driver/mysql,在接口、驅動的設計上”database/sql”的實現非常優秀,對于類似設計有很多值得我們借鑒的地方,比如beego框架cache的實現模式就是借鑒了這個包的實現;”database/sql”除了定義接口外還有一個重要的功能:連接池,我們在實現其他網絡通信時也可以借鑒其實現。

連接池的作用這里就不再多說了,我們先從一個簡單的示例看下”database/sql”怎么用:

package mainimport( "fmt" "database/sql" _ "github.com/go-sql-driver/mysql")func main(){ db, err := sql.Open("mysql", "username:password@tcp(host)/db_name?charset=utf8&allowOldPasswords=1") if err != nil {  fmt.Println(err)  return } defer db.Close() rows,err := db.Query("select * from test") for rows.Next(){  //row.Scan(...) } rows.Close()}

用法很簡單,首先Open打開一個數據庫,然后調用Query、Exec執行數據庫操作,github.com/go-sql-driver/mysql具體實現了database/sql/driver的接口,所以最終具體的數據庫操作都是調用github.com/go-sql-driver/mysql實現的方法,同一個數據庫只需要調用一次Open即可,下面根據具體的操作分析下”database/sql”都干了哪些事。

1.驅動注冊

import _ "github.com/go-sql-driver/mysql"前面的”_”作用時不需要把該包都導進來,只執行包的init()方法,mysql驅動正是通過這種方式注冊到”database/sql”中的:

//github.com/go-sql-driver/mysql/driver.gofunc init() { sql.Register("mysql", &MySQLDriver{})}type MySQLDriver struct{}func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { ...}

init()通過Register()方法將mysql驅動添加到sql.drivers(類型:make(map[string]driver.Driver))中,MySQLDriver實現了driver.Driver接口:

//database/sql/sql.gofunc Register(name string, driver driver.Driver) { driversMu.Lock() defer driversMu.Unlock() if driver == nil {  panic("sql: Register driver is nil") } if _, dup := drivers[name]; dup {  panic("sql: Register called twice for driver " + name) } drivers[name] = driver}//database/sql/driver/driver.gotype Driver interface { // Open returns a new connection to the database. // The name is a string in a driver-specific format. // // Open may return a cached connection (one previously // closed), but doing so is unnecessary; the sql package // maintains a pool of idle connections for efficient re-use. // // The returned connection is only used by one goroutine at a // time. Open(name string) (Conn, error)}

假如我們同時用到多種數據庫,就可以通過調用sql.Register將不同數據庫的實現注冊到sql.drivers中去,用的時候再根據注冊的name將對應的driver取出。

2.連接池實現

先看下連接池整體處理流程:

golang,sql,連接池

2.1 初始化DB

db, err := sql.Open("mysql", "username:password@tcp(host)/db_name?charset=utf8&allowOldPasswords=1")

sql.Open()是取出對應的db,這時mysql還沒有建立連接,只是初始化了一個sql.DB結構,這是非常重要的一個結構,所有相關的數據都保存在此結構中;Open同時啟動了一個connectionOpener協程,后面再具體分析其作用。

type DB struct { driver driver.Driver //數據庫實現驅動 dsn string //數據庫連接、配置參數信息,比如username、host、password等 numClosed uint64 mu   sync.Mutex   //鎖,操作DB各成員時用到 freeConn  []*driverConn  //空閑連接 connRequests []chan connRequest //阻塞請求隊列,等連接數達到最大限制時,后續請求將插入此隊列等待可用連接 numOpen  int     //已建立連接或等待建立連接數 openerCh chan struct{}  //用于connectionOpener closed  bool dep   map[finalCloser]depSet lastPut  map[*driverConn]string // stacktrace of last conn's put; debug only maxIdle  int     //最大空閑連接數 maxOpen  int     //數據庫最大連接數 maxLifetime time.Duration   //連接最長存活期,超過這個時間連接將不再被復用 cleanerCh chan struct{}}

maxIdle(默認值2)、maxOpen(默認值0,無限制)、maxLifetime(默認值0,永不過期)可以分別通過SetMaxIdleConns、SetMaxOpenConns、SetConnMaxLifetime設定。

2.2 獲取連接

上面說了Open時是沒有建立數據庫連接的,只有等用的時候才會實際建立連接,獲取可用連接的操作有兩種策略:cachedOrNewConn(有可用空閑連接則優先使用,沒有則創建)、alwaysNewConn(不管有沒有空閑連接都重新創建),下面以一個query的例子看下具體的操作:

rows, err := db.Query("select * from test")

database/sql/sql.go:

func (db *DB) Query(query string, args ...interface{}) (*Rows, error) { var rows *Rows var err error //maxBadConnRetries = 2 for i := 0; i < maxBadConnRetries; i++ {  rows, err = db.query(query, args, cachedOrNewConn)  if err != driver.ErrBadConn {   break  } } if err == driver.ErrBadConn {  return db.query(query, args, alwaysNewConn) } return rows, err}func (db *DB) query(query string, args []interface{}, strategy connReuseStrategy) (*Rows, error) { ci, err := db.conn(strategy) if err != nil {  return nil, err } //到這已經獲取到了可用連接,下面進行具體的數據庫操作 return db.queryConn(ci, ci.releaseConn, query, args)}

數據庫連接由db.query()獲?。?/p>

func (db *DB) conn(strategy connReuseStrategy) (*driverConn, error) { db.mu.Lock() if db.closed {  db.mu.Unlock()  return nil, errDBClosed } lifetime := db.maxLifetime //從freeConn取一個空閑連接 numFree := len(db.freeConn) if strategy == cachedOrNewConn && numFree > 0 {  conn := db.freeConn[0]  copy(db.freeConn, db.freeConn[1:])  db.freeConn = db.freeConn[:numFree-1]  conn.inUse = true  db.mu.Unlock()  if conn.expired(lifetime) {   conn.Close()   return nil, driver.ErrBadConn  }  return conn, nil } //如果沒有空閑連接,而且當前建立的連接數已經達到最大限制則將請求加入connRequests隊列, //并阻塞在這里,直到其它協程將占用的連接釋放或connectionOpenner創建 if db.maxOpen > 0 && db.numOpen >= db.maxOpen {  // Make the connRequest channel. It's buffered so that the  // connectionOpener doesn't block while waiting for the req to be read.  req := make(chan connRequest, 1)  db.connRequests = append(db.connRequests, req)  db.mu.Unlock()  ret, ok := <-req //阻塞  if !ok {   return nil, errDBClosed  }  if ret.err == nil && ret.conn.expired(lifetime) { //連接過期了   ret.conn.Close()   return nil, driver.ErrBadConn  }  return ret.conn, ret.err } db.numOpen++ //上面說了numOpen是已經建立或即將建立連接數,這里還沒有建立連接,只是樂觀的認為后面會成功,失敗的時候再將此值減1 db.mu.Unlock() ci, err := db.driver.Open(db.dsn) //調用driver的Open方法建立連接 if err != nil { //創建連接失敗  db.mu.Lock()  db.numOpen-- // correct for earlier optimism  db.maybeOpenNewConnections() //通知connectionOpener協程嘗試重新建立連接,否則在db.connRequests中等待的請求將一直阻塞,知道下次有連接建立  db.mu.Unlock()  return nil, err } db.mu.Lock() dc := &driverConn{  db:  db,  createdAt: nowFunc(),  ci:  ci, } db.addDepLocked(dc, dc) dc.inUse = true db.mu.Unlock() return dc, nil}

總結一下上面獲取連接的過程: 

* step1:首先檢查下freeConn里是否有空閑連接,如果有且未超時則直接復用,返回連接,如果沒有或連接已經過期則進入下一步; 

* step2:檢查當前已經建立及準備建立的連接數是否已經達到最大值,如果達到最大值也就意味著無法再創建新的連接了,當前請求需要在這等著連接釋放,這時當前協程將創建一個channel:chan connRequest,并將其插入db.connRequests隊列,然后阻塞在接收chan connRequest上,等到有連接可用時這里將拿到釋放的連接,檢查可用后返回;如果還未達到最大值則進入下一步; 

* step3:創建一個連接,首先將numOpen加1,然后再創建連接,如果等到創建完連接再把numOpen加1會導致多個協程同時創建連接時一部分會浪費,所以提前將numOpen占住,創建失敗再將其減掉;如果創建連接成功則返回連接,失敗則進入下一步 

* step4:創建連接失敗時有一個善后操作,當然并不僅僅是將最初占用的numOpen數減掉,更重要的一個操作是通知connectionOpener協程根據db.connRequests等待的長度創建連接,這個操作的原因是:

numOpen在連接成功創建前就加了1,這時候如果numOpen已經達到最大值再有獲取conn的請求將阻塞在step2,這些請求會等著先前進來的請求釋放連接,假設先前進來的這些請求創建連接全部失敗,那么如果它們直接返回了那些等待的請求將一直阻塞在那,因為不可能有連接釋放(極限值,如果部分創建成功則會有部分釋放),直到新請求進來重新成功創建連接,顯然這樣是有問題的,所以maybeOpenNewConnections將通知connectionOpener根據db.connRequests長度及可創建的最大連接數重新創建連接,然后將新創建的連接發給阻塞的請求。

注意:如果maxOpen=0將不會有請求阻塞等待連接,所有請求只要從freeConn中取不到連接就會新創建。

另外Query、Exec有個重試機制,首先優先使用空閑連接,如果2次取到的連接都無效則嘗試新創建連接。

獲取到可用連接后將調用具體數據庫的driver處理sql。

2.3 釋放連接

數據庫連接在被使用完成后需要歸還給連接池以供其它請求復用,釋放連接的操作是:putConn():

func (db *DB) putConn(dc *driverConn, err error) { ... //如果連接已經無效,則不再放入連接池 if err == driver.ErrBadConn {  db.maybeOpenNewConnections()  dc.Close() //這里最終將numOpen數減掉  return } ... //正常歸還 added := db.putConnDBLocked(dc, nil) ...}func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { if db.maxOpen > 0 && db.numOpen > db.maxOpen {  return false } //有等待連接的請求則將連接發給它們,否則放入freeConn if c := len(db.connRequests); c > 0 {  req := db.connRequests[0]  // This copy is O(n) but in practice faster than a linked list.  // TODO: consider compacting it down less often and  // moving the base instead?  copy(db.connRequests, db.connRequests[1:])  db.connRequests = db.connRequests[:c-1]  if err == nil {   dc.inUse = true  }  req <- connRequest{   conn: dc,   err: err,  }  return true } else if err == nil && !db.closed && db.maxIdleConnsLocked() > len(db.freeConn) {  db.freeConn = append(db.freeConn, dc)  db.startCleanerLocked()  return true } return false}

釋放的過程: 

* step1:首先檢查下當前歸還的連接在使用過程中是否發現已經無效,如果無效則不再放入連接池,然后檢查下等待連接的請求數新建連接,類似獲取連接時的異常處理,如果連接有效則進入下一步; 

* step2:檢查下當前是否有等待連接阻塞的請求,有的話將當前連接發給最早的那個請求,沒有的話則再判斷空閑連接數是否達到上限,沒有則放入freeConn空閑連接池,達到上限則將連接關閉釋放。 

* step3:(只執行一次)啟動connectionCleaner協程定時檢查feeConn中是否有過期連接,有則剔除。

有個地方需要注意的是,Query、Exec操作用法有些差異:

a.Exec(update、insert、delete等無結果集返回的操作)調用完后會自動釋放連接;

b.Query(返回sql.Rows)則不會釋放連接,調用完后仍然占有連接,它將連接的所屬權轉移給了sql.Rows,所以需要手動調用close歸還連接,即使不用Rows也得調用rows.Close(),否則可能導致后續使用出錯,如下的用法是錯誤的:

//錯誤db.SetMaxOpenConns(1)db.Query("select * from test")row,err := db.Query("select * from test") //此操作將一直阻塞//正確db.SetMaxOpenConns(1)r,_ := db.Query("select * from test")r.Close() //將連接的所屬權歸還,釋放連接row,err := db.Query("select * from test")//other oprow.Close()

附:請求一個連接的函數有好幾種,執行完畢處理連接的方式稍有差別,大致如下:

  • db.Ping() 調用完畢后會馬上把連接返回給連接池。
  • db.Exec() 調用完畢后會馬上把連接返回給連接池,但是它返回的Result對象還保留這連接的引用,當后面的代碼需要處理結果集的時候連接將會被重用。
  • db.Query() 調用完畢后會將連接傳遞給sql.Rows類型,當然后者迭代完畢或者顯示的調用.Clonse()方法后,連接將會被釋放回到連接池。
  • db.QueryRow()調用完畢后會將連接傳遞給sql.Row類型,當.Scan()方法調用之后把連接釋放回到連接池。
  • db.Begin() 調用完畢后將連接傳遞給sql.Tx類型對象,當.Commit()或.Rollback()方法調用后釋放連接。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美亚洲国产视频小说| 亚洲精品一区二三区不卡| 国产欧美最新羞羞视频在线观看| 九九热精品在线| 91成人免费观看网站| 成人欧美一区二区三区在线湿哒哒| 亚洲精品mp4| 亚洲日本aⅴ片在线观看香蕉| 欧美国产高跟鞋裸体秀xxxhd| 成人欧美一区二区三区在线湿哒哒| 精品久久久久久久久久久久久久| 亚洲人高潮女人毛茸茸| 亚洲国产精品va在线看黑人动漫| 久久精品国产91精品亚洲| 日韩电视剧在线观看免费网站| 久久人人爽人人爽爽久久| 亚洲最大福利视频| 日本中文字幕不卡免费| 国产成人aa精品一区在线播放| 亚洲精品小视频| 中文字幕欧美专区| 国产精品va在线| 亚洲区免费影片| 亚洲欧美综合精品久久成人| 亚洲国产高清高潮精品美女| 91沈先生作品| 中文字幕日韩精品在线| 91久久精品美女| 日韩欧美在线观看视频| 国产999在线观看| 日韩激情av在线免费观看| 欧美国产欧美亚洲国产日韩mv天天看完整| 欧美激情精品久久久| 成人a在线观看| 久久成人一区二区| 欧美精品在线免费| 日韩欧美亚洲一二三区| 国产成+人+综合+亚洲欧美丁香花| 在线观看欧美日韩国产| www.国产精品一二区| 亚洲乱码一区av黑人高潮| 亚洲精品xxxx| 日韩三级影视基地| 久久久久久久久综合| 亚洲tv在线观看| 亚洲视屏在线播放| 亚洲欧美综合图区| 久久九九亚洲综合| 91免费人成网站在线观看18| 亚洲视屏在线播放| 日韩精品视频观看| 97涩涩爰在线观看亚洲| 亚洲一级片在线看| 欧美性生交大片免网| 国产精品黄色av| 亚洲天堂色网站| 中文字幕欧美在线| 亚州欧美日韩中文视频| 亚洲网站在线观看| 国产国语videosex另类| 久久精品国产69国产精品亚洲| 欧美一级片久久久久久久| 日韩视频一区在线| 欧洲成人在线观看| 国产精品综合不卡av| 欧美综合激情网| 国产成人综合亚洲| 亚洲精品视频在线观看视频| 麻豆精品精华液| 国产亚洲一区精品| 欧美天天综合色影久久精品| 久久国产精品亚洲| 日韩精品久久久久久福利| 国产一区二区免费| 日韩的一区二区| 精品国产一区二区三区久久久| 欧美香蕉大胸在线视频观看| 欧美日韩国产精品一区二区不卡中文| 555www成人网| 性欧美激情精品| 欧美另类交人妖| 久久精品在线播放| 欧美激情久久久久| 久久久久国色av免费观看性色| 91av视频在线| 久久99久国产精品黄毛片入口| 久久这里有精品视频| 久久亚洲精品成人| 亚洲国产成人91精品| 狠狠色狠狠色综合日日五| 亚洲天堂视频在线观看| 亚洲美女激情视频| 成人国产精品久久久久久亚洲| 正在播放欧美一区| 欧美精品videos另类日本| 色综久久综合桃花网| 黄色精品在线看| 国产日韩综合一区二区性色av| 国产精品你懂得| 亚洲成av人乱码色午夜| 精品久久香蕉国产线看观看亚洲| 国产成人av网址| 国内精品伊人久久| 欧美wwwwww| 国产欧美日韩中文字幕| 亚洲天堂av网| 91精品视频播放| 亚洲最大中文字幕| 欧美亚洲一区在线| 久久久久五月天| 国产精品伦子伦免费视频| 亚洲第一区中文字幕| 国产mv久久久| 亚洲在线第一页| 欧美精品激情视频| 久精品免费视频| 成人免费视频xnxx.com| 国产成人精品免费视频| 一区二区三区高清国产| 91精品视频在线看| 日韩男女性生活视频| 国产精品亚洲美女av网站| 国产精品久久久久久久久久| 亚洲精品电影网在线观看| 欧美日韩国产成人在线| 亚洲第一av网站| 亚洲已满18点击进入在线看片| 国产丝袜精品视频| 欧美日韩亚洲精品内裤| 久久青草精品视频免费观看| 欧美高清在线视频观看不卡| 最新91在线视频| 国产精品成av人在线视午夜片| 66m—66摸成人免费视频| 亚洲成**性毛茸茸| 亚洲欧美国产日韩中文字幕| 亚洲激情国产精品| 欧美大秀在线观看| 欧美国产激情18| 亚洲精品久久久久久久久| 成人激情视频网| 亚洲全黄一级网站| 国产精品夜间视频香蕉| 亚洲精品日韩丝袜精品| 国产97在线|日韩| 成人国内精品久久久久一区| 久久男人av资源网站| 亚洲综合精品一区二区| 久久精视频免费在线久久完整在线看| 精品久久久久久久久久| 亚洲天堂免费观看| 色先锋久久影院av| 日韩不卡中文字幕| 国产成人极品视频| 伊人久久免费视频| 欧美猛少妇色xxxxx| 国产精品久久久久久久久久三级| 国产成人一区三区| yellow中文字幕久久| 久久777国产线看观看精品| 全球成人中文在线| 国产小视频国产精品| 亚洲女人天堂av| 国外色69视频在线观看|