摘要:上面代碼的過程為表示向數(shù)據(jù)庫發(fā)送一個(gè),非常重要,遍歷使用,把遍歷到的數(shù)據(jù)存入變量使用遍歷完成后檢查。有幾點(diǎn)需要注意檢查遍歷是否有結(jié)果集未關(guān)閉前,底層的連接處于繁忙狀態(tài)。包有自動(dòng)重試等功能。
概述因?yàn)樽罱趯W(xué)習(xí)Go,所以找了revel這個(gè)框架來學(xué)習(xí),感覺和php的面向?qū)ο笥泻艽蟛煌evel沒有提供db mapping的組件,所以在github上搜了很多ORM來學(xué)習(xí),在jmoiron/sqlx中發(fā)現(xiàn)了一篇比較詳細(xì)介紹database/sql這個(gè)包的文章,拿來和大家分享。本文并不是按字句的翻譯,如果哪里表述不清楚建議閱讀原文 原文地址
sql.DB不是一個(gè)連接,它是數(shù)據(jù)庫的抽象接口。它可以根據(jù)driver打開關(guān)閉數(shù)據(jù)庫連接,管理連接池。正在使用的連接被標(biāo)記為繁忙,用完后回到連接池等待下次使用。所以,如果你沒有把連接釋放回連接池,會(huì)導(dǎo)致過多連接使系統(tǒng)資源耗盡。
使用DB 導(dǎo)入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的第一個(gè)參數(shù)是driver名稱,第二個(gè)參數(shù)是driver連接數(shù)據(jù)庫的信息,各個(gè)driver可能不同。DB不是連接,并且只有當(dāng)需要使用時(shí)才會(huì)創(chuàng)建連接,如果想立即驗(yàn)證連接,需要用Ping()方法,如下:
err = db.Ping() if err != nil { // do something here }
sql.DB的設(shè)計(jì)就是用來作為長(zhǎng)連接使用的。不要頻繁O(jiān)pen, Close。比較好的做法是,為每個(gè)不同的datastore建一個(gè)DB對(duì)象,保持這些對(duì)象Open。如果需要短連接,那么把DB作為參數(shù)傳入function,而不要在function中Open, Close。
讀取DB如果方法包含Query,那么這個(gè)方法是用于查詢并返回rows的。其他情況應(yīng)該用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()表示向數(shù)據(jù)庫發(fā)送一個(gè)query,defer rows.Close()非常重要,遍歷rows使用rows.Next(), 把遍歷到的數(shù)據(jù)存入變量使用rows.Scan(), 遍歷完成后檢查error。有幾點(diǎn)需要注意:
檢查遍歷是否有error
結(jié)果集(rows)未關(guān)閉前,底層的連接處于繁忙狀態(tài)。當(dāng)遍歷讀到最后一條記錄時(shí),會(huì)發(fā)生一個(gè)內(nèi)部EOF錯(cuò)誤,自動(dòng)調(diào)用rows.Close(),但是如果提前退出循環(huán),rows不會(huì)關(guān)閉,連接不會(huì)回到連接池中,連接也不會(huì)關(guān)閉。所以手動(dòng)關(guān)閉非常重要。rows.Close()可以多次調(diào)用,是無害操作。
單行Queryerr在Scan后才產(chǎn)生,所以可以如下寫:
var name string err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) if err != nil { log.Fatal(err) } fmt.Println(name)修改數(shù)據(jù),事務(wù)
一般用Prepared Statements和Exec()完成INSERT, UPDATE, DELETE操作。
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 ", lastId, rowCnt)事務(wù)
db.Begin()開始事務(wù),Commit() 或 Rollback()關(guān)閉事務(wù)。Tx從連接池中取出一個(gè)連接,在關(guān)閉之前都是使用這個(gè)連接。Tx不能和DB層的BEGIN, COMMIT混合使用。
如果你需要通過多條語句修改連接狀態(tài),你必須使用Tx,例如:
創(chuàng)建僅對(duì)單個(gè)連接可見的臨時(shí)表
設(shè)置變量,例如SET @var := somevalue
改變連接選項(xiàng),例如字符集,超時(shí)
Prepared Statements Prepared Statements and Connection在數(shù)據(jù)庫層面,Prepared Statements是和單個(gè)數(shù)據(jù)庫連接綁定的。客戶端發(fā)送一個(gè)有占位符的statement到服務(wù)端,服務(wù)器返回一個(gè)statement ID,然后客戶端發(fā)送ID和參數(shù)來執(zhí)行statement。
在GO中,連接不直接暴露,你不能為連接綁定statement,而是只能為DB或Tx綁定。database/sql包有自動(dòng)重試等功能。當(dāng)你生成一個(gè)Prepared Statement
自動(dòng)在連接池中綁定到一個(gè)空閑連接
Stmt對(duì)象記住綁定了哪個(gè)連接
執(zhí)行Stmt時(shí),嘗試使用該連接。如果不可用,例如連接被關(guān)閉或繁忙中,會(huì)自動(dòng)re-prepare,綁定到另一個(gè)連接。
這就導(dǎo)致在高并發(fā)的場(chǎng)景,過度使用statement可能導(dǎo)致statement泄漏,statement持續(xù)重復(fù)prepare和re-prepare的過程,甚至?xí)_(dá)到服務(wù)器端statement數(shù)量上限。
某些操作使用了PS,例如db.Query(sql, param1, param2), 并在最后自動(dòng)關(guān)閉statement。
有些場(chǎng)景不適合用statement:
數(shù)據(jù)庫不支持。例如Sphinx,MemSQL。他們支持MySQL wire protocol, 但不支持"binary" protocol。
statement不需要重用很多次,并且有其他方法保證安全。例子
在Transaction中使用PSPS在Tx中唯一綁定一個(gè)連接,不會(huì)re-prepare。
Tx和statement不能分離,在DB中創(chuàng)建的statement也不能在Tx中使用,因?yàn)樗麄儽囟ú皇鞘褂猛粋€(gè)連接使用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在關(guān)閉時(shí)就無法找到連接。所以必須在Tx commit或rollback之前關(guān)閉statement。
處理Error 循環(huán)Rows的Error如果循環(huán)中發(fā)生錯(cuò)誤會(huì)自動(dòng)運(yùn)行rows.Close(),用rows.Err()接收這個(gè)錯(cuò)誤,Close方法可以多次調(diào)用。循環(huán)之后判斷error是非常必要的。
for rows.Next() { // ... } if err = rows.Err(); err != nil { // handle the error here }關(guān)閉Resultsets時(shí)的error
如果你在rows遍歷結(jié)束之前退出循環(huán),必須手動(dòng)關(guān)閉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 string err = 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,一般應(yīng)用中不存在的情況都需要多帶帶處理。此外,Query返回的錯(cuò)誤都會(huì)延遲到Scan被調(diào)用,所以應(yīng)該寫成如下代碼:
var name string err = 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)
把空結(jié)果當(dāng)做Error處理是為了強(qiáng)行讓程序員處理結(jié)果為空的情況
分析數(shù)據(jù)庫Error各個(gè)數(shù)據(jù)庫處理方式不太一樣,mysql為例:
if driverErr, ok := err.(*mysql.MySQLError); ok { // Now the error number is accessible directly if driverErr.Number == 1045 { // Handle the permission-denied error } }
MySQLError, Number都是DB特異的,別的數(shù)據(jù)庫可能是別的類型或字段。這里的數(shù)字可以替換為常量,例如這個(gè)包 MySQL error numbers maintained by VividCortex
連接錯(cuò)誤 NULL值處理簡(jiǎn)單說就是設(shè)計(jì)數(shù)據(jù)庫的時(shí)候不要出現(xiàn)null,處理起來非常費(fèi)力。Null的type很有限,例如沒有sql.NullUint64; null值沒有默認(rèn)零值。
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()的使用,用于處理不能得知結(jié)果字段個(gè)數(shù)或類型的情況,例如:
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 afterwards vals := 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. }關(guān)于連接池
避免錯(cuò)誤操作,例如LOCK TABLE后用 INSERT會(huì)死鎖,因?yàn)閮蓚€(gè)操作不是同一個(gè)連接,insert的連接沒有table lock。
當(dāng)需要連接,且連接池中沒有可用連接時(shí),新的連接就會(huì)被創(chuàng)建。
默認(rèn)沒有連接上限,你可以設(shè)置一個(gè),但這可能會(huì)導(dǎo)致數(shù)據(jù)庫產(chǎn)生錯(cuò)誤“too many connections”
db.SetMaxIdleConns(N)設(shè)置最大空閑連接數(shù)
db.SetMaxOpenConns(N)設(shè)置最大打開連接數(shù)
長(zhǎng)時(shí)間保持空閑連接可能會(huì)導(dǎo)致db timeout
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/17475.html
摘要:基于優(yōu)雅的語法和其強(qiáng)大的并發(fā)性,我開啟我的學(xué)習(xí)之旅。男女河南省商水縣等城鎮(zhèn)林村插入數(shù)據(jù)成功創(chuàng)建表創(chuàng)建表成功啦第二種方式總結(jié)今天的總結(jié)開發(fā)的冰山一角,接下來還需要學(xué)習(xí)很多。 奧術(shù)大師 做了近5年的android開發(fā),最近項(xiàng)目也是不怎么忙,空閑的時(shí)候總會(huì)思考一些事情,不過作為移動(dòng)開發(fā),我個(gè)人覺得很有必要學(xué)習(xí)后臺(tái)開發(fā),由于公司是Go語言開發(fā)的,了解go語言一段時(shí)間后,我發(fā)現(xiàn)go語言的強(qiáng)大。基...
摘要:在上一篇文章中我們講解了使用語言的標(biāo)準(zhǔn)庫包操作數(shù)據(jù)庫的過程,雖然使用包操作數(shù)據(jù)也是挺方便的,但是需要自己寫每一條語句,因此我們可能會(huì)自己再度進(jìn)行封裝,以便更好地使用,而使用現(xiàn)有語言開源框架則是代替自己封裝的一個(gè)更好的方式。在上一篇文章中我們講解了使用Go語言的標(biāo)準(zhǔn)庫sql/database包操作數(shù)據(jù)庫的過程,雖然使用sql/database包操作數(shù)據(jù)也是挺方便的,但是需要自己寫每一條SQL語...
摘要:在上一篇文章中我們講解了使用語言的標(biāo)準(zhǔn)庫包操作數(shù)據(jù)庫的過程,雖然使用包操作數(shù)據(jù)也是挺方便的,但是需要自己寫每一條語句,因此我們可能會(huì)自己再度進(jìn)行封裝,以便更好地使用,而使用現(xiàn)有語言開源框架則是代替自己封裝的一個(gè)更好的方式。在上一篇文章中我們講解了使用Go語言的標(biāo)準(zhǔn)庫sql/database包操作數(shù)據(jù)庫的過程,雖然使用sql/database包操作數(shù)據(jù)也是挺方便的,但是需要自己寫每一條SQL語...
閱讀 3556·2021-09-22 15:50
閱讀 3245·2019-08-30 15:54
閱讀 2761·2019-08-30 14:12
閱讀 3068·2019-08-30 11:22
閱讀 2091·2019-08-29 11:16
閱讀 3586·2019-08-26 13:43
閱讀 1200·2019-08-23 18:33
閱讀 931·2019-08-23 18:32