網頁

2022/1/12

Golang sql defer Rows.Close()

Go使用database/sql查詢資料庫時應總是defer Rows.Close()確保關閉回傳的result set並釋放連線。


下面使用sql的DB.Query()查詢資料庫並回傳rows, err。先判斷是否有err,然後才呼叫defer rows.Close()。因為如果有errrows為nil會導致panic。

package main
...
func main() {
    db := connect() // get DB object

    rows, err := db.Query("SELECT ... ")
    if err != nil { // check if err exists
        panic(err)
    }
    defer rows.Close() // then call defer rows.Close() to avoid runtime panic of invoking nil rows object

    for rows.Next() { // iterate each row of result set
        ...
    }

}

雖然Rows.Next()遍歷result set到最後一筆後內部會呼叫Rows.Close()關閉result set,但若因為某些原因在最後一筆前就停止則 Rows.Next()內部不會觸發Rows.Close(),因此明確地手動調用defer rows.Close()以確保關閉result set並釋放連線。

節錄Rows.Next()原始碼如下。

sql.go

// Next prepares the next result row for reading with the Scan method. It
// returns true on success, or false if there is no next result row or an error
// happened while preparing it. Err should be consulted to distinguish between
// the two cases.
//
// Every call to Scan, even the first one, must be preceded by a call to Next.
func (rs *Rows) Next() bool {
    var doClose, ok bool
    withLock(rs.closemu.RLocker(), func() {
        doClose, ok = rs.nextLocked()
    })
    if doClose {
        rs.Close()
    }
    return ok
}

func (rs *Rows) nextLocked() (doClose, ok bool) {
    ...
    rs.lasterr = rs.rowsi.Next(rs.lastcols)
    if rs.lasterr != nil {
        // Close the connection if there is a driver error.
        if rs.lasterr != io.EOF {
            return true, false
        }
        nextResultSet, ok := rs.rowsi.(driver.RowsNextResultSet)
        if !ok {
            return true, false
        }
        // The driver is at the end of the current result set.
        // Test to see if there is another result set after the current one.
        // Only close Rows if there is no further result sets to read.
        if !nextResultSet.HasNextResultSet() {
            doClose = true
        }
        return doClose, false
    }
    ...
}


沒有留言:

張貼留言