關於Golang中database/sql包的學習筆記(轉載)

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

概述

sql.DB不是一個串連,它是資料庫的抽象介面。它可以根據driver開啟關閉資料庫連接,管理串連池。正在使用的串連被標記為繁忙,用完後回到串連池等待下次使用。所以,如果你沒有把串連釋放回串連池,會導致過多串連使系統資源耗盡。

 

使用DB

匯入driver

這裡使用的是MySQL drivers

import (    "database/sql"    _ "github.com/go-sql-driver/mysql")

 

串連DB

func main() {    db, err := sql.Open("mysql",        "user:password@tcp(127.0.0.1:3306)/hello")    if err != nil {        log.Fatal(err)    }    defer db.Close()}

sql.Open的第一個參數是driver名稱,第二個參數是driver串連資料庫的資訊,各個driver可能不同。DB不是串連,並且只有當需要使用時才會建立串連,如果想立即驗證串連,需要用Ping()方法,如下:

err = db.Ping()if err != nil {    // do something here}

sql.DB的設計就是用來作為長串連使用的。不要頻繁Open, Close。比較好的做法是,為每個不同的datastore建一個DB對象,保持這些對象Open。如果需要短串連,那麼把DB作為參數傳入function,而不要在function中Open, Close。

 

讀取DB

如果方法包含Query,那麼這個方法是用於查詢並返回rows的。其他情況應該用Exec()

var (    id int    name string)rows, err := db.Query("select id, name from users where id = ?", 1)if err != nil {    log.Fatal(err)}defer rows.Close()for rows.Next() {    err := rows.Scan(&id, &name)    if err != nil {        log.Fatal(err)    }    log.Println(id, name)}err = rows.Err()if err != nil {    log.Fatal(err)}

上面代碼的過程為:db.Query()表示向資料庫發送一個query,defer rows.Close()非常重要,遍曆rows使用rows.Next(), 把遍曆到的資料存入變數使用rows.Scan(), 遍曆完成後檢查error。有幾點需要注意:

  • 檢查遍曆是否有error
  • 結果集(rows)未關閉前,底層的串連處於繁忙狀態。當遍曆讀到最後一條記錄時,會發生一個內部EOF錯誤,自動調用rows.Close(),但是如果提前退出迴圈,rows不會關閉,串連不會回到串連池中,串連也不會關閉。所以手動關閉非常重要。rows.Close()可以多次調用,是無害操作。

 

單行Query

err在Scan後才產生,所以可以如下寫:

var name stringerr = db.QueryRow("select name from users where id = ?", 1).Scan(&name)if err != nil {    log.Fatal(err)}fmt.Println(name)

 

修改資料,事務

一般用Prepared Statements和Exec()完成INSERTUPDATEDELETE操作。

stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")if err != nil {    log.Fatal(err)}res, err := stmt.Exec("Dolly")if err != nil {    log.Fatal(err)}lastId, err := res.LastInsertId()if err != nil {    log.Fatal(err)}rowCnt, err := res.RowsAffected()if err != nil {    log.Fatal(err)}log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)

事務

db.Begin()開始事務,Commit() 或 Rollback()關閉事務。Tx從串連池中取出一個串連,在關閉之前都是使用這個串連。Tx不能和DB層的BEGINCOMMIT混合使用。

如果你需要通過多條語句修改串連狀態,你必須使用Tx,例如:

  • 建立僅對單個串連可見的暫存資料表
  • 設定變數,例如SET @var := somevalue
  • 改變串連選項,例如字元集,逾時

 

Prepared Statements

Prepared Statements and Connection

在資料庫層面,Prepared Statements是和單個資料庫連接綁定的。用戶端發送一個有預留位置的statement到服務端,伺服器返回一個statement ID,然後用戶端發送ID和參數來執行statement。

在GO中,串連不直接暴露,你不能為串連綁定statement,而是只能為DB或Tx綁定。database/sql包有自動重試等功能。當你產生一個Prepared Statement

  1. 自動在串連池中綁定到一個空閑串連
  2. Stmt對象記住綁定了哪個串連
  3. 執行Stmt時,嘗試使用該串連。如果不可用,例如串連被關閉或繁忙中,會自動re-prepare,綁定到另一個串連。

這就導致在高並發的情境,過度使用statement可能導致statement泄漏,statement持續重複prepare和re-prepare的過程,甚至會達到伺服器端statement數量上限。

某些操作使用了PS,例如db.Query(sql, param1, param2), 並在最後自動關閉statement。

有些情境不適合用statement:

  1. 資料庫不支援。例如Sphinx,MemSQL。他們支援MySQL wire protocol, 但不支援"binary" protocol。
  2. statement不需要重用很多次,並且有其他方法保證安全。例子

在Transaction中使用PS

PS在Tx中唯一綁定一個串連,不會re-prepare。

Tx和statement不能分離,在DB中建立的statement也不能在Tx中使用,因為他們必定不是使用同一個串連使用Tx必須十分小心,例如下面的代碼:

tx, err := db.Begin()if err != nil {    log.Fatal(err)}defer tx.Rollback()stmt, err := tx.Prepare("INSERT INTO foo VALUES (?)")if err != nil {    log.Fatal(err)}defer stmt.Close() // danger!for i := 0; i < 10; i++ {    _, err = stmt.Exec(i)    if err != nil {        log.Fatal(err)    }}err = tx.Commit()if err != nil {    log.Fatal(err)}// stmt.Close() runs here!

*sql.Tx一旦釋放,串連就回到串連池中,這裡stmt在關閉時就無法找到串連。所以必須在Tx commit或rollback之前關閉statement。

 

處理Error

迴圈Rows的Error

如果迴圈中發生錯誤會自動運行rows.Close(),用rows.Err()接收這個錯誤,Close方法可以多次調用。迴圈之後判斷error是非常必要的。

for rows.Next() {    // ...}if err = rows.Err(); err != nil {    // handle the error here}

關閉Resultsets時的error

如果你在rows遍曆結束之前退出迴圈,必須手動關閉Resultset,並且接收error。

for rows.Next() {    // ...    break; // whoops, rows is not closed! memory leak...}// do the usual "if err = rows.Err()" [omitted here]...// it's always safe to [re?]close here:if err = rows.Close(); err != nil {    // but what should we do if there's an error?    log.Println(err)}

QueryRow()的error

var name stringerr = db.QueryRow("select name from users where id = ?", 1).Scan(&name)if err != nil {    log.Fatal(err)}fmt.Println(name)

如果id為1的不存在,err為sql.ErrNoRows,一般應用中不存在的情況都需要單獨處理。此外,Query返回的錯誤都會延遲到Scan被調用,所以應該寫成如下代碼:

var name stringerr = db.QueryRow("select name from users where id = ?", 1).Scan(&name)if err != nil {    if err == sql.ErrNoRows {        // there were no rows, but otherwise no error occurred    } else {        log.Fatal(err)    }}fmt.Println(name)

把空結果當做Error處理是為了強行讓程式員處理結果為空白的情況

分析資料庫Error

各個資料庫處理方式不太一樣,mysql為例:

if driverErr, ok := err.(*mysql.MySQLError); ok {     // Now the error number is accessible directly    if driverErr.Number == 1045 {        // Handle the permission-denied error    }}

MySQLErrorNumber都是DB特異的,別的資料庫可能是別的類型或欄位。這裡的數字可以替換為常量,例如這個包 MySQL error numbers maintained by VividCortex

串連錯誤

NULL值處理

簡單說就是設計資料庫的時候不要出現null,處理起來非常費力。Null的type很有限,例如沒有sql.NullUint64; null值沒有預設零值。

for rows.Next() {    var s sql.NullString    err := rows.Scan(&s)    // check err    if s.Valid {       // use s.String    } else {       // NULL value    }}

 

未知Column

rows.Columns()的使用,用於處理不能得知結果欄位個數或類型的情況,例如:

cols, err := rows.Columns()if err != nil {    // handle the error} else {    dest := []interface{}{ // Standard MySQL columns        new(uint64), // id        new(string), // host        new(string), // user        new(string), // db        new(string), // command        new(uint32), // time        new(string), // state        new(string), // info    }    if len(cols) == 11 {        // Percona Server    } else if len(cols) > 8 {        // Handle this case    }    err = rows.Scan(dest...)    // Work with the values in dest}
cols, err := rows.Columns() // Remember to check err afterwardsvals := make([]interface{}, len(cols))for i, _ := range cols {    vals[i] = new(sql.RawBytes)}for rows.Next() {    err = rows.Scan(vals...)    // Now you can check each element of vals for nil-ness,    // and you can use type introspection and type assertions    // to fetch the column into a typed variable.}

 

關於串連池

  1. 避免錯誤操作,例如LOCK TABLE後用 INSERT會死結,因為兩個操作不是同一個串連,insert的串連沒有table lock。
  2. 當需要串連,且串連池中沒有可用串連時,新的串連就會被建立。
  3. 預設沒有串連上限,你可以設定一個,但這可能會導致資料庫產生錯誤“too many connections”
  4. db.SetMaxIdleConns(N)設定最大空閑串連數
  5. db.SetMaxOpenConns(N)設定最大開啟串連數
  6. 長時間保持空閑串連可能會導致db timeout

聯繫我們

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