最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2018年09月07日 17:24:25   作者:wzFreewz  
database/sql是golang的標準庫之一,它提供了一系列接口方法,用于訪問關系數據庫。下面這篇文章主要給大家介紹了關于golang sql連接池用法的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面來一起看看吧

前言

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

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

package main

import(
 "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執(zhí)行數據庫操作,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"前面的”_”作用時不需要把該包都導進來,只執(zhí)行包的init()方法,mysql驅動正是通過這種方式注冊到”database/sql”中的:

//github.com/go-sql-driver/mysql/driver.go
func 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.go
func 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.go
type 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.連接池實現

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

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協(xié)程,后面再具體分析其作用。

type DB struct {
 driver driver.Driver //數據庫實現驅動
 dsn string //數據庫連接、配置參數信息,比如username、host、password等
 numClosed uint64

 mu   sync.Mutex   //鎖,操作DB各成員時用到
 freeConn  []*driverConn  //空閑連接
 connRequests []chan connRequest //阻塞請求隊列,等連接數達到最大限制時,后續(xù)請求將插入此隊列等待可用連接
 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(有可用空閑連接則優(yōu)先使用,沒有則創(chuàng)建)、alwaysNewConn(不管有沒有空閑連接都重新創(chuàng)建),下面以一個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()獲取:

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隊列,
 //并阻塞在這里,直到其它協(xié)程將占用的連接釋放或connectionOpenner創(chuàng)建
 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 { //創(chuàng)建連接失敗
  db.mu.Lock()
  db.numOpen-- // correct for earlier optimism
  db.maybeOpenNewConnections() //通知connectionOpener協(xié)程嘗試重新建立連接,否則在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:檢查當前已經建立及準備建立的連接數是否已經達到最大值,如果達到最大值也就意味著無法再創(chuàng)建新的連接了,當前請求需要在這等著連接釋放,這時當前協(xié)程將創(chuàng)建一個channel:chan connRequest,并將其插入db.connRequests隊列,然后阻塞在接收chan connRequest上,等到有連接可用時這里將拿到釋放的連接,檢查可用后返回;如果還未達到最大值則進入下一步;

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

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

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

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

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

獲取到可用連接后將調用具體數據庫的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
 }
 //有等待連接的請求則將連接發(fā)給它們,否則放入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:首先檢查下當前歸還的連接在使用過程中是否發(fā)現已經無效,如果無效則不再放入連接池,然后檢查下等待連接的請求數新建連接,類似獲取連接時的異常處理,如果連接有效則進入下一步;

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

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

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

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

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

//錯誤
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 op
row.Close()

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

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

總結

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

相關文章

  • Go語言開發(fā)redis封裝及簡單使用詳解

    Go語言開發(fā)redis封裝及簡單使用詳解

    這篇文章主要為大家介紹了Go語言開發(fā)redis的封裝及簡單使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪
    2021-11-11
  • Go語言Http?Server框架實現一個簡單的httpServer

    Go語言Http?Server框架實現一個簡單的httpServer

    這篇文章主要為大家介紹了Go語言Http?Server框架實現一個簡單的httpServer抽象,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • 一文初探?Goroutine?與?channel基本用法

    一文初探?Goroutine?與?channel基本用法

    這篇文章主要為大家介紹了一文初探?Goroutine?與?channel基本用法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • golang實現mysql數據庫事務的提交與回滾

    golang實現mysql數據庫事務的提交與回滾

    這篇文章主要介紹了golang實現mysql數據庫事務的提交與回滾,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Windows下升級go版本過程詳解

    Windows下升級go版本過程詳解

    這篇文章主要為大家介紹了Windows下升級go版本過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-03-03
  • 詳解golang中的結構體編解碼神器Mapstructure庫

    詳解golang中的結構體編解碼神器Mapstructure庫

    mapstructure是GO字典(map[string]interface{})和Go結構體之間轉換的編解碼工具,這篇文章主要為大家介紹一下Mapstructure庫的相關使用,希望對大家有所幫助
    2023-09-09
  • 簡單聊聊Go?for?range中容易踩的坑

    簡單聊聊Go?for?range中容易踩的坑

    for循環(huán)問題,在面試中經常都會被問到,并且在實際業(yè)務項目中也經常用到for循環(huán),要是沒用好,一不下心就掉坑,本文就來講講Go?for?range中容易踩的坑吧
    2023-03-03
  • Go設計模式之狀態(tài)模式圖文詳解

    Go設計模式之狀態(tài)模式圖文詳解

    狀態(tài)模式是一種行為設計模式, 讓你能在一個對象的內部狀態(tài)變化時改變其行為, 使其看上去就像改變了自身所屬的類一樣,本文將通過一些圖片來給大家詳細的介紹一下Go的狀態(tài)模式,需要的朋友可以參考下
    2023-08-08
  • Go中Goroutines輕量級并發(fā)的特性及效率探究

    Go中Goroutines輕量級并發(fā)的特性及效率探究

    這篇文章主要為大家介紹了Go中Goroutines輕量級并發(fā)的特性及效率探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-12-12
  • Golang實現AES對稱加密的過程詳解

    Golang實現AES對稱加密的過程詳解

    AES是一個對稱密碼,旨在取代DES成為廣泛使用的標準,本文給大家分享Golang實現AES對稱加密的過程,本文附有Golang實現AES加密ECB模式的源碼,感興趣的朋友跟隨小編一起學習下吧
    2021-05-05

最新評論

大渡口区| 鹤岗市| 类乌齐县| 黄平县| 乐东| 平乡县| 莱西市| 东乡| 清苑县| 镇原县| 长顺县| 望江县| 鄯善县| 叙永县| 湟中县| 陆河县| 湘西| 广安市| 镇原县| 搜索| 凤城市| 临邑县| 天峻县| 彭泽县| 景德镇市| 曲沃县| 土默特右旗| 安新县| 清流县| 正阳县| 赣州市| 通许县| 崇左市| 临泉县| 望江县| 临猗县| 隆德县| 荔浦县| 清水河县| 怀来县| 九龙坡区|