dive into golang database/sql(3)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

上一章中我們一起探討了golangdatabase/sql包中如何擷取一個真實的資料庫連接。當我們拿到一個資料庫連接之後就可以開始真正的資料庫操作了。本章講繼續深入,一起探討底層是如何進行資料庫操作的。

上一章中我們說到:

db.Query()

實際上分為兩步:

  • 擷取資料庫連接
  • 在此串連上利用driver進行實際的DB操作
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.queryConn

其實sql包最核心的就是維護了串連池,對於實際的操作,都是利用Driver去完成。因此代碼實現也一樣,堅持一個原則:

組裝Driver需要的參數,執行Driver的方法

db.queryConn虛擬碼如下:

func (db *DB) queryConn(dc *driverConn, releaseConn func(error), query string, args []interface{}) (*Rows, error) {    if queryer, ok := dc.ci.(driver.Queryer); ok {        dargs, err := driverArgs(nil, args)        if err != nil {            releaseConn(err)            return nil, err        }        dc.Lock()        rowsi, err := queryer.Query(query, dargs)        dc.Unlock()        if err != driver.ErrSkip {            if err != nil {                releaseConn(err)                return nil, err            }            // Note: ownership of dc passes to the *Rows, to be freed            // with releaseConn.            rows := &Rows{                dc:          dc,                releaseConn: releaseConn,                rowsi:       rowsi,            }            return rows, nil        }    }    dc.Lock()    si, err := dc.ci.Prepare(query)    dc.Unlock()    if err != nil {        releaseConn(err)        return nil, err    }    ds := driverStmt{dc, si}    rowsi, err := rowsiFromStatement(ds, args...)    if err != nil {        dc.Lock()        si.Close()        dc.Unlock()        releaseConn(err)        return nil, err    }    // Note: ownership of ci passes to the *Rows, to be freed    // with releaseConn.    rows := &Rows{        dc:          dc,        releaseConn: releaseConn,        rowsi:       rowsi,        closeStmt:   si,    }    return rows, nil}

queryConn的實現可以分為兩部分來看:

  • Driver實現了Queryer介面
  • Driver沒有實現該介面,走Stmt三部曲

Queryer

Queryer介面很能體現golang內部命名interface的風格,比如ReaderWriter等,Queryer要求實現一個Query方法。如果Driver實現了這個Query方法,那麼sql包只需要把它需要的參數準備好然後傳給它就行了。

driverArgs用來準備Query需要的參數,實際上就是把各種類型的值利用反射轉換成它所在類型的最大類型。這句話有點不好理解,簡單點講就是把int int8 uint uint16 int16等轉換為int64,把floatX轉換為float64。最終,driverArgs會把所有類型轉化為以下幾種

  • []byte
  • bool
  • float64
  • int64
  • string
  • time.Time

思考①:

為什麼要進行資料轉換

準備好參數之後就調用Driver實現好的Query方法。

dc.Lock()rowsi, err := queryer.Query(query, dargs)dc.Unlock()

最終的請求很簡單,因為工作量都在driver,但是問題也來了

問題②:

這裡為什麼要加鎖?

每個Query都會先擷取串連再進行Query,如果串連池是安全執行緒的,對於取到串連的後續行為還需要加鎖嗎?

調用Driver的Query方法執行完Query請求就拿到了rowsi(Driver.Rows),將它包一層包成sql.Rows返回給caller。

// Note: ownership of dc passes to the *Rows, to be freed// with releaseConn.rows := &Rows{    dc:          dc,    releaseConn: releaseConn,    rowsi:       rowsi,}return rows, nil

至此呢,一個真實的請求就處理完畢了。實際上對於sql包來說非常簡單,工作量都在各種不同的Driver裡。

Stmt

正如文檔所說,Queryer介面是可選的:

Queryer is an optional interface that may be implemented by a Conn.

If a Conn does not implement Queryer, the sql package's DB.Query will first prepare a query, execute the statement, and then close the statement.

所以對於那些偷懶的Driver來說,執行一個Query請求就得用Stmt了。

dc.Lock()si, err := dc.ci.Prepare(query)dc.Unlock()

Prepare方法產生一個Stmt。當然這裡同樣有相同的問題需要你思考一下,這裡加鎖是否有必要。可以先看看Stmt的定義:

// Stmt is a prepared statement. It is bound to a Conn and not// used by multiple goroutines concurrently.type Stmt interface {    // Close closes the statement.    //    // As of Go 1.1, a Stmt will not be closed if it's in use    // by any queries.    Close() error    // NumInput returns the number of placeholder parameters.    //    // If NumInput returns >= 0, the sql package will sanity check    // argument counts from callers and return errors to the caller    // before the statement's Exec or Query methods are called.    //    // NumInput may also return -1, if the driver doesn't know    // its number of placeholders. In that case, the sql package    // will not sanity check Exec or Query argument counts.    NumInput() int    // Exec executes a query that doesn't return rows, such    // as an INSERT or UPDATE.    Exec(args []Value) (Result, error)    // Query executes a query that may return rows, such as a    // SELECT.    Query(args []Value) (Rows, error)}

可以看到Stmt的方法也很簡單,ExecQuery是最終執行請求會需要用到的方法。NumInput用來統計sql語句中預留位置的數量。

很多人之前可能都比較疑惑Stmt是用來幹什麼的,看到這裡應該明白了。事實上Stmt就是一個sql語句的模板,模板固定,只是參數在變化,這種情境就特別適合用Stmt,你不再需要把sql語句複製幾遍。

拿到Stmt之後,通過執行StmtQuery方法,也能拿到結果rows。進行Query之前也需要buildParams以及檢查參數和sql語句的placeholder是否匹配等,所以進行了一個簡單封裝:

ds := driverStmt{dc, si}rowsi, err := rowsiFromStatement(ds, args...)

si就是Stmt了為什麼還要包成driverStmt,而driverStmt又是什麼呢?其實主要還是為了在rowsiFromStatement方法中執行Query是加鎖。參照Queryer中的代碼,執行Query時是需要加鎖的,這把鎖是dc提供的,所以封裝一個driverStmt變相讓Stmt有了加鎖的方法:

// driverStmt associates a driver.Stmt with the// *driverConn from which it came, so the driverConn's lock can be// held during calls.type driverStmt struct {    sync.Locker // the *driverConn    si          driver.Stmt}

rowsiFromStatement內部執行完Query後也拿到了Driver.Rows,如之前一樣封裝成sql.Rows返回給caller就好。

至此,我們已經一起探究了golang的sql包是如何處理Query請求的了。但是還是有一個問題一直貫穿著整個過程,就是:

為什麼要加鎖

如果只是看Query方法可以還不好理解,但是看了Stmt之後應該就可以理解了。Stmt是可以多次利用的,每個Stmt包含了conn,可以把一個Stmt看成一個資料庫連接。有了資料庫連接的概念,使用者如果在多個goroutine中使用這個Stmt,就會有並發的問題,因此通過Stmt進行Query或者Exec是需要加鎖的。

但是對於實現了Queryer介面的Driver來說,使用者調用db.Query後每次都會取新的串連然後再進行Query,最後返回一個Rows。對使用者來說直接Query的整個過程並沒有串連的概念,因此我個人覺得是安全的。這裡需不需要加鎖有待商榷。如果覺得需要加鎖歡迎留言和我討論

Tx

Tx實際上和上面是一樣的,主要也是建立時先請求一個conn,然後基於這個conn封裝一個Tx對象。後續的操作都要依賴於底層的資料庫。

Tx需要特別注意的是:

如果後端的資料庫proxy,就不能使用資料庫事務

這和golang無關,所有語言都一樣。因為我們無法保證我們對一個事務的請求都落到同一台機器。

關於golang的sql包,到這兒也將告一段落了。其實它的核心就是:

  • 維護了資料庫連接池
  • 定義了一系列介面規範,讓Driver可以面向介面進行開發

接下來有時間的話,我寫一篇文章來分析go-sql-driver/mysql,不過底層的實現相對而言會比較無聊,主要都是實現mysql通訊協定的規範,按照規範收發報文。

golang1.8 sql包中新增了不少介面,這很令人期待,更簡化了我們對於資料庫的使用,方便進行一些進階的封裝,而不用層層反射。不過目前各Driver的支援是一個大問題

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.