go標準庫的學習-database/sql

參考:https://studygolang.com/pkgdochtml

導入方式:mysql

import "database/sql"

sql包提供了保證SQL或類SQL數據庫的泛用接口。git

使用sql包時必須注入(至少)一個數據庫驅動。相關可見go標準庫的學習-database/sql/drivergithub

 

1.空值golang

參考:https://yq.aliyun.com/articles/178898?utm_content=m_29337sql

當用戶認爲數據庫中的某一列不會出現空值(即nil)而將該列設置爲基本類型,而後在從數據庫中接收寫入數據時若是獲得了空值nil,程序就會崩潰。數據庫

⚠️空值(即nil)和零值是不一樣的,Go語言的每個變量都有着默認零值,當數據的零值沒有意義時,能夠用零值來表示空值。安全

空值的解決辦法有:socket

1)使用零值tcp

若是數據自己從語義上就不會出現零值,或者根本不區分零值和空值,那麼最簡便的方法就是使用零值來表示空值

2)數據庫層面解決辦法

經過對列添加NOT NULL約束,能夠確保任何結果都不會爲空。或者,經過在SQL中使用COALESCE來爲NULL設定默認值。

3)自定義處理邏輯,以下

type Scanner

type Scanner interface { // Scan方法從數據庫驅動獲取一個值。 //
    // 參數src的類型保證爲以下類型之一: //
    // int64 // float64 // bool // []byte // string // time.Time // nil - 表示NULL值 //
    // 若是不能不丟失信息的保存一個值,應返回錯誤。
    Scan(src interface{}) error }

Scanner接口會被Rows或Row等的Scan方法使用。

任何實現了Scanner接口的類型,均可以經過定義本身的Scan函數來處理空值問題,好比:

  • Rows或Row的Scan方法其實就是實現從數據庫驅動中獲取一個值(該值會轉換成src的類型),並將其存儲到src,src知足driver.Value類型
  • 而NullString、NullBool等的Scan方法則是將輸入的值轉換成對應的NullString類型並存儲下來

4)使用額外的標記字段,以下面的 Valid字段

database\sql提供了四種基本可空數據類型:使用基本類型和一個布爾標記的複合結構體表示可空值

type NullString

type NullString struct { String string Valid bool // 若是String不是NULL則Valid爲真
}

NullString表明一個可爲NULL的字符串。NullString實現了Scanner接口,所以能夠做爲Rows/Row的Scan方法的參數保存掃描結果:

var s NullString err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&s) //即將獲得的name值轉換成s.String類型並存儲到&s中 ... if s.Valid {//若是name值非空值 // use s.String
} else {//若是name值爲空值 // NULL value
}

func (*NullString) Scan

func (ns *NullString) Scan(value interface{}) error

Scan實現了Scanner接口。

func (NullString) Value

func (ns NullString) Value() (driver.Value, error)

Value實現了driver.Valuer接口。

其實現源碼爲:

type NullString struct { String string Valid bool // Valid is true if String is not NULL
} // Scan implements the Scanner interface.
func (ns *NullString) Scan(value interface{}) error { if value == nil { //若是 ns.String, ns.Valid = "", false
        return nil } ns.Valid = true
    return convertAssign(&ns.String, value) } // Value implements the driver Valuer interface.
func (ns NullString) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } return ns.String, nil }

其中使用到了一個函數convertAssign:

//該函數的做用是將src的值複製到dest上,並將src的類型轉換成dest的類型,可轉換則返回nil;不然返回錯誤 //dest是一個指針類型
func convertAssign(dest, src interface{}) error

所以上面的Scan函數的做用就是將非nil的傳入參數值value轉換成ns.String類型,並存儲在&ns.String中,同時設置ns.Valid爲true

type NullBool

type NullBool struct { Bool bool Valid bool // 若是Bool不是NULL則Valid爲真
}

NullBool表明一個可爲NULL的布爾值。NullBool實現了Scanner接口,所以能夠做爲Rows/Row的Scan方法的參數保存掃描結果,相似NullString。

func (*NullBool) Scan

func (n *NullBool) Scan(value interface{}) error

Scan實現了Scanner接口。

func (NullBool) Value

func (n NullBool) Value() (driver.Value, error)

Value實現了driver.Valuer接口。

type NullInt64

type NullInt64 struct { Int64 int64 Valid bool // 若是Int64不是NULL則Valid爲真
}

NullInt64表明一個可爲NULL的int64值。NullInt64實現了Scanner接口,所以能夠做爲Rows/Row的Scan方法的參數保存掃描結果,相似NullString。

func (*NullInt64) Scan

func (n *NullInt64) Scan(value interface{}) error

Scan實現了Scanner接口。

func (NullInt64) Value

func (n NullInt64) Value() (driver.Value, error)

Value實現了driver.Valuer接口。

type NullFloat64

type NullFloat64 struct { Float64 float64 Valid bool // 若是Float64不是NULL則Valid爲真
}

NullFloat64表明一個可爲NULL的float64值。NullFloat64實現了Scanner接口,所以能夠做爲Rows/Row的Scan方法的參數保存掃描結果,相似NullString。

func (*NullFloat64) Scan

func (n *NullFloat64) Scan(value interface{}) error

Scan實現了Scanner接口。

func (NullFloat64) Value

func (n NullFloat64) Value() (driver.Value, error)

Value實現了driver.Valuer接口。

 

type RawBytes

type RawBytes []byte

RawBytes是一個字節切片,保管對內存的引用,爲數據庫自身所使用。在Scaner接口的Scan方法寫入RawBytes數據後,該切片只在限次調用Next、Scan或Close方法以前合法。

 

2.DB

1)func Register

func Register(name string, driver driver.Driver)

Register註冊並命名一個數據庫,能夠在Open函數中使用該命名啓用該驅動。

若是 Register註冊同一名稱兩次,或者driver參數爲nil,會致使panic。

該函數用來註冊數據庫驅動。當第三方開發者開發數據庫驅動時,都會實現init函數,在init裏面調用這個Register(name string, driver driver.Driver)完成本驅動的註冊,好比

1>sqlite3的驅動:

//http://github.com/mattn/go-sqlite3驅動
func init(){ sql.Register("sqlite3", &SQLiteDriver{}) }

2>mysql的驅動

//http://github.com/mikespook/mymysql驅動
var d = Driver{proto : "tcp", raddr : "127.0.0.1:3306"} func init(){ Register("SET NAMES utf8") sql.Register("mymysql", &d) }

由上可見第三方數據庫驅動都是經過這個函數來註冊本身的數據庫驅動名稱及相應的driver實現。

上面的例子實現的都是註冊一個驅動,該函數還可以實現同時註冊多個數據庫驅動,只要這些驅動不重複,經過一個map來存儲用戶定義的相應驅動

var drivers = make(map[string]driver.Driver) drivers[name] = driver

 

在使用database/sql接口和第三方庫時常常看見以下:

import( "database/sql" _ "github.com/mattn/go-sqlite3" //上面定義的sqlite3驅動包 )

裏面的_的做用就是說明引入了"github.com/mattn/go-sqlite3"該包,可是不直接使用包裏面的函數或變量,會先調用包中的init函數。這種使用方式僅讓導入的包作初始化,而不使用包中其餘功能

 

2)type DB

type DB struct { // 內含隱藏或非導出字段
}

DB是一個數據庫(操做)句柄,表明一個具備零到多個底層鏈接的鏈接池。它能夠安全的被多個go程同時使用。

sql.DB不是一個鏈接,它是數據庫的抽象接口。它能夠根據driver驅動打開關閉數據庫鏈接,管理鏈接池。正在使用的鏈接被標記爲繁忙,用完後回到鏈接池等待下次使用。因此,若是你沒有把鏈接釋放回鏈接池,會致使過多鏈接使系統資源耗盡。

sql包會自動建立和釋放鏈接;它也會維護一個閒置鏈接的鏈接池。若是數據庫具備單鏈接狀態的概念,該狀態只有在事務中被觀察時纔可信。

一旦調用了DB.Begin,返回的Tx會綁定到單個鏈接。當調用事務Tx的Commit或Rollback後,該事務使用的鏈接會歸還到DB的閒置鏈接池中。

鏈接池的大小能夠用SetMaxIdleConns方法控制。

func Open

func Open(driverName, dataSourceName string) (*DB, error)

Open打開一個dirverName指定的數據庫,dataSourceName指定數據源,通常包至少括數據庫文件名和(可能的)鏈接信息。

大多數用戶會經過數據庫特定的鏈接幫助函數打開數據庫,返回一個*DB。Go標準庫中沒有數據庫驅動。參見http://golang.org/s/sqldrivers獲取第三方驅動。

 Open函數不建立與數據庫的鏈接,也不驗證其參數。它可能會延遲到你第一次調用該數據庫時纔回去真正建立與數據庫的鏈接。因此若是要當即檢查數據源的名稱是否合法,或者數據庫是否實際可用,應調用返回值的Ping方法。

func (*DB) Ping

func (db *DB) Ping() error

Ping檢查與數據庫的鏈接是否仍有效,若是須要會建立鏈接。

func (*DB) Close

func (db *DB) Close() error

Close關閉數據庫,釋聽任何打開的資源。通常不會關閉DB,由於DB句柄一般被多個go程共享,並長期活躍。

 

舉例,正確是不會報錯:

package main import( "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() if err != nil{ panic(err) } //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } }

若是寫錯密碼,則會返回:

userdeMBP:go-learning user$ go run test.go 2019/02/20 19:51:00 Error 1045: Access denied for user 'root'@'localhost' (using password: YES) exit status 1

可見調用sql.Open()函數時並無報錯,是調用db.Ping()函數時才報出的密碼錯誤

 

返回的DB能夠安全的被多個go程同時使用,並會維護自身的閒置鏈接池。這樣一來,Open函數只需調用一次。不多須要關閉DB,由於sql.DB對象是爲了長鏈接設計的,不要頻繁使用Open()和Close()函數,不然會致使各類錯誤

所以應該爲每一個待訪問的數據庫建立一個sql.DB實例,並在用完前保留它。若是須要短鏈接使用,那麼能夠將其做爲函數的參數傳遞給別的function的參數使用,而不是在這個function中調用Open()和Close()再創建已經建立的sql.DB實例,或者將其設置爲全局變量。

  • 第一個參數是調用的驅動名,好比下面的例子中使用的是github.com/go-sql-driver/mysql中註冊的驅動"mysql"
  • 第二個參數依賴與特定驅動的語法,用來鏈接數據庫,一般是URL的形式,如"root:user78@/test"

func (*DB) Driver

func (db *DB) Driver() driver.Driver

Driver方法返回數據庫下層驅動。

 

下面的四個函數用於進行數據庫操做:

func (*DB) Exec

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Exec執行一次命令(包括查詢、刪除、更新、插入等),不返回數據集,返回的結果是Result,Result接口容許獲取執行結果的元數據。參數args表示query中的佔位參數。

func (*DB) Query

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Query執行一次查詢,返回多行結果(即Rows),通常用於執行select命令。參數args表示query中的佔位參數。

上面兩個的差異在與:Query會返回查詢結果Rows,Exec不會返回查詢結果,只會返回一個結果的狀態Result

因此通常進行不須要返回值的DDL和增刪改等操做時會使用Exec,查詢則使用Query。固然這主要仍是取決因而否須要返回值

func (*DB) QueryRow

func (db *DB) QueryRow(query string, args ...interface{}) *Row

QueryRow執行一次查詢,並指望返回最多一行結果(即Row)。QueryRow老是返回非nil的值,直到返回值的Scan方法被調用時,纔會返回被延遲的錯誤。(如:未找到結果)

func (*DB) Prepare

func (db *DB) Prepare(query string) (*Stmt, error)

Prepare建立一個準備好的狀態用於以後的查詢和命令,即準備一個須要屢次使用的語句,供後續執行用。返回值能夠同時執行多個查詢和命令。

 

func (*DB) Begin

func (db *DB) Begin() (*Tx, error)

Begin開始一個事務。隔離水平由數據庫驅動決定。

 

舉一個簡單例子:

首先先在mysql中建立數據庫test,並生成兩個表,一個是用戶表userinfo,一個是關聯用戶信息表userdetail。使用workbench進行建立,首先建立數據庫test:

CREATE SCHEMA `test` DEFAULT CHARACTER SET utf8 ;

而後建立表:

use test; create table `userinfo` ( `uid` int(10) not null auto_increment, `username` varchar(64) null default null, `department` varchar(64) null default null, `created` date null default null, primary key (`uid`) ); create table `userdetail`( `uid` int(10) not null default '0', `intro` text null, `profile` text null, primary key (`uid`) );

接下來就示範怎麼使用database/sql接口對數據庫進行增刪改查操做:

固然運行前首先須要下載驅動:

go get -u github.com/go-sql-driver/mysql

固然,若是你鏈接的是sqlite3數據庫,那麼你要下載的驅動是:

http://github.com/mattn/go-sqlite3

舉例;

package main import( "fmt" "database/sql" _ "github.com/go-sql-driver/mysql" ) func checkErr(err error){ if err != nil{ panic(err) } } func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"  defer db.Close() checkErr(err) //插入數據 stmt, err := db.Prepare("insert userinfo set username = ?,department=?,created=?") checkErr(err) //執行準備好的Stmt res, err := stmt.Exec("user1", "computing", "2019-02-20") checkErr(err) //獲取上一個,即上面insert操做的ID id, err := res.LastInsertId() checkErr(err) fmt.Println(id) //1 //更新數據 stmt, err =db.Prepare("update userinfo set username=? where uid=?") checkErr(err) res, err = stmt.Exec("user1update", id) checkErr(err) affect, err := res.RowsAffected() checkErr(err) fmt.Println(affect) //1 //查詢數據 rows, err := db.Query("select * from userinfo") checkErr(err) for rows.Next() { //做爲循環條件來迭代獲取結果集Rows 
     //從結果集中獲取一行結果 err = rows.Scan(&uid, &username, &department, &created) //1 user1update computing 2019-02-20 checkErr(err) fmt.Println(uid, username, department, created) }   defer rows.Close() //關閉結果集,釋放連接 //刪除數據 stmt, err = db.Prepare("delete from userinfo where uid=?") checkErr(err) res, err = stmt.Exec(id) checkErr(err) affect, err = res.RowsAffected() checkErr(err) fmt.Println(affect) //1 }

返回:

userdeMBP:go-learning user$ go run test.go 1 1 1 user1update computing 2019-02-20 1

上面代碼使用的函數的做用分別是:

1.sql.Open()函數用來打開一個註冊過的數據庫驅動,go-sql-driver/mysql中註冊了mysql這個數據庫驅動,第二個參數是DNS(Data Source Name),它是go-sql-driver/mysql定義的一些數據庫鏈接和配置信息,其支持下面的幾種格式:

user@unix(/path/to/socket)/dbname?charset=utf8 user:password@tcp(localhost:5555)/dbname?charset=utf8 user:password@/dbname user:password@tcp([de:ad:be::ca:fe]:80)/dbname

2.db.Prepare()函數用來返回準備要執行的sql操做,而後返回準備完畢的執行狀態

3.db.Query()函數用來直接執行Sql並返回Rows結果

4.stmt.Exec()函數用來執行stmt準備好的SQL語句,而後返回Result

⚠️sql中傳入的參數都是=?對應的數據,這樣作能夠在必定程度上防止SQL注入

 

type Result

type Result interface { // LastInsertId返回一個數據庫生成的迴應命令的整數。 // 當插入新行時,通常來自一個"自增"列。 // 不是全部的數據庫都支持該功能,該狀態的語法也各有不一樣。
 LastInsertId() (int64, error) // RowsAffected返回被update、insert或delete命令影響的行數。 // 不是全部的數據庫都支持該功能。
 RowsAffected() (int64, error) }

Result是對已執行的SQL命令的總結。

 

func (*DB) SetMaxOpenConns

func (db *DB) SetMaxOpenConns(n int)

SetMaxOpenConns設置與數據庫創建鏈接的最大數目。

若是n大於0且小於最大閒置鏈接數,會將最大閒置鏈接數減少到匹配最大開啓鏈接數的限制。

若是n <= 0,不會限制最大開啓鏈接數,默認爲0(無限制)。

func (*DB) SetMaxIdleConns

func (db *DB) SetMaxIdleConns(n int)

SetMaxIdleConns設置鏈接池中的最大閒置鏈接數。

若是n大於最大開啓鏈接數,則新的最大閒置鏈接數會減少到匹配最大開啓鏈接數的限制。

若是n <= 0,不會保留閒置鏈接。

 

3.Row

上面的DB的函數Query()和QueryRow()會返回*ROWs和*ROW,所以下面就是如何去獲得返回結果的更多詳細的信息

1)type Row

type Row struct { // 內含隱藏或非導出字段
}

QueryRow方法返回Row,表明單行查詢結果。

func (*Row) Scan

func (r *Row) Scan(dest ...interface{}) error

Scan將該行查詢結果各列分別保存進dest參數指定的值中。若是該查詢匹配多行,Scan會使用第一行結果並丟棄其他各行。若是沒有匹配查詢的行,Scan會返回ErrNoRows。

舉例:

一開始數據庫中爲空,所以調用Scan會返回錯誤:

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() if err != nil{ panic(err) } //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } //查詢數據
    var uid int 
    var username, department, created string err = db.QueryRow("select * from userinfo").Scan(&uid, &username, &department, &created) switch { case err == sql.ErrNoRows: log.Printf("No user with that ID.") //返回 2019/02/21 10:38:33 No user with that ID.
    case err != nil: log.Fatal(err) default: fmt.Printf("Username is %s\n", username) } }

所以若是先插入數據再調用QueryRow則不會出錯了:

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() if err != nil{ log.Fatal(err) } //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } stmt, err := db.Prepare("insert userinfo set username =?,department=?,created=?") if err != nil{ log.Fatal(err) } _, err = stmt.Exec("testQueryRow", "computing", "2019-02-21") if err != nil{ log.Fatal(err) } //查詢數據
    var uid int 
    var username, department, created string err = db.QueryRow("select * from userinfo").Scan(&uid, &username, &department, &created) switch { case err == sql.ErrNoRows: log.Printf("No user with that ID.")
    case err != nil: log.Fatal(err) default: fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created) } }

返回:

userdeMBP:go-learning user$ go run test.go Uid is 3, username is testQueryRow, department is computing, created at 2019-02-21

 

2)type Rows

type Rows struct { // 內含隱藏或非導出字段
}

Rows是查詢的結果。它的遊標指向結果集的第零行,使用Next方法來遍歷各行結果:

rows, err := db.Query("SELECT ...") ... defer rows.Close() for rows.Next() { var id int
    var name string err = rows.Scan(&id, &name) ... } err = rows.Err() // 在退出迭代後檢查錯誤
...

func (*Rows) Columns

func (rs *Rows) Columns() ([]string, error)

Columns返回列名。若是Rows已經關閉會返回錯誤。

func (*Rows) Scan

func (rs *Rows) Scan(dest ...interface{}) error

Scan將當前行各列結果填充進dest指定的各個值中,用於在迭代中獲取一行結果。

若是某個參數的類型爲*[]byte,Scan會保存對應數據的拷貝,該拷貝爲調用者全部,能夠安全的,修改或無限期的保存。若是參數類型爲*RawBytes能夠避免拷貝;參見RawBytes的文檔獲取其使用的約束。

若是某個參數的類型爲*interface{},Scan會不作轉換的拷貝底層驅動提供的值。若是值的類型爲[]byte,會進行數據的拷貝,調用者能夠安全使用該值。

func (*Rows) Next

func (rs *Rows) Next() bool

Next準備用於Scan方法的下一行結果。若是成功會返回true,若是沒有下一行或者出現錯誤會返回false。Err()方法應該被調用以區分這兩種狀況。

每一次調用Scan方法,甚至包括第一次調用該方法,都必須在前面先調用Next方法。

func (*Rows) Close

func (rs *Rows) Close() error

Close關閉Rows,阻止對其更多的列舉。 若是Next方法返回false,Rows會自動關閉,知足檢查Err方法結果的條件。Close方法是冪等的(即屢次調用不會出錯),不影響Err方法的結果。

用於關閉結果集Rows。結果集引用了數據庫鏈接,並會從中讀取結果。讀取完以後必須關閉它才能避免資源泄露。只要結果集仍然打開着,相應的底層鏈接就處於忙碌狀態,不能被其餘查詢使用。

func (*Rows) Err

func (rs *Rows) Err() error

Err返回可能的、在迭代時出現的錯誤,即用於在退出迭代後檢查錯誤。Err需在顯式或隱式調用Close方法後調用,即若是Next方法返回false,Rows會自動關閉,至關於調用了Close()。

正常狀況下迭代退出是由於內部產生的EOF錯誤(即數據讀取完畢),使得下一次rows.Next() == false,從而終止循環;在迭代結束後要檢查錯誤,以確保迭代是由於數據讀取完畢,而非其餘「真正」錯誤而結束的。

舉例:

包括上面的例子,這裏再插入一條數據,這樣數據庫中就有兩條數據了

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() if err != nil{ log.Fatal(err) } //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } stmt, err := db.Prepare("insert userinfo set username =?,department=?,created=?") if err != nil{ log.Fatal(err) } _, err = stmt.Exec("testQuery", "data mining", "2019-02-21") if err != nil{ log.Fatal(err) } //查詢數據
    rows, err := db.Query("select * from userinfo") if err != nil{ log.Fatal(err) } defer rows.Close() //迭代結果
    var uid int 
    var username, department, created string
    for rows.Next() { if err = rows.Scan(&uid, &username, &department, &created); err != nil { log.Fatal(err) } fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created) } //查看迭代時是否出錯以及出的是什麼錯
    if rows.Err() != nil { log.Fatal(err) } }

返回:

userdeMBP:go-learning user$ go run test.go Uid is 3, username is testQueryRow, department is computing, created at 2019-02-21 Uid is 4, username is testQuery, department is data mining, created at 2019-02-21

 

4.Stmt

在調用db.Prepare()後會返回*Stmt,即準備好的語句,通常一個會屢次進行查詢的語句就應該將其設置爲準備好的語句。

Stmt是和單個數據庫直接綁定的。客戶端會發送一個帶有佔位符,如?的SQL語句的Stmt到服務端,而後服務端會返回一個Stmt ID,說明給你綁定的鏈接是哪個。而後以後當客戶端要執行該Stmt時,就會發送ID和參數來綁定鏈接並執行操做。

要注意的是不能直接爲Stmt綁定鏈接,鏈接只能與DB和Tx綁定,當咱們生成一個Stmt時,首先它會自動在鏈接池中綁定一個空閒鏈接,而後Stmt會記住該鏈接,而後以後執行時嘗試使用這個鏈接,若是不可用,如鏈接繁忙或關閉,則會從新準備語句並再綁定一個新的鏈接

Stmt中能夠執行的方法與db中的方法十分相似

type Stmt

type Stmt struct { // 內含隱藏或非導出字段
}

Stmt是準備好的狀態。Stmt能夠安全的被多個go程同時使用。

func (*Stmt) Exec

func (s *Stmt) Exec(args ...interface{}) (Result, error)

Exec使用提供的參數執行準備好的命令狀態,返回Result類型的該狀態執行結果的總結。

func (*Stmt) Query

func (s *Stmt) Query(args ...interface{}) (*Rows, error)

Query使用提供的參數執行準備好的查詢狀態,返回Rows類型查詢結果。

func (*Stmt) QueryRow

func (s *Stmt) QueryRow(args ...interface{}) *Row

QueryRow使用提供的參數執行準備好的查詢狀態。若是在執行時遇到了錯誤,該錯誤會被延遲,直到返回值的Scan方法被調用時才釋放。返回值老是非nil的。若是沒有查詢到結果,*Row類型返回值的Scan方法會返回ErrNoRows;不然,Scan方法會掃描結果第一行並丟棄其他行。

示例用法:

var name string err := nameByUseridStmt.QueryRow(id).Scan(&name)

func (*Stmt) Close

func (s *Stmt) Close() error

Close關閉狀態。

舉例:

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() if err != nil{ log.Fatal(err) } //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } stmt1, err := db.Prepare("insert userinfo set username =?,department=?,created=?") if err != nil{ log.Fatal(err) } _, err = stmt1.Exec("testStmtExecAndQueryRow", "accounting", "2019-02-21") if err != nil{ log.Fatal(err) } defer stmt1.Close() stmt2, err := db.Prepare("select * from userinfo where uid =?") if err != nil{ log.Fatal(err) } //查詢數據
    var uid int 
    var username, department, created string err = stmt2.QueryRow(5).Scan(&uid, &username, &department, &created) if err != nil{ log.Fatal(err) } fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created) defer stmt2.Close() }

返回:

userdeMBP:go-learning user$ go run test.go Uid is 5, username is testStmtExecAndQueryRow, department is accounting, created at 2019-02-21

 

5.Tx

db.Begin()函數會返回*Tx。Go中事務(Tx)是一個持有數據庫鏈接的對象,它容許用戶在同一個鏈接上執行上面提到的各種操做。

使用它的緣由是:Tx上執行的方法都保證是在同一個底層鏈接上執行的,止癢對鏈接狀態的修改將會一直對後續的操做起做用

然而DB的方法就不會保證是在同一條鏈接上執行,若是以前的鏈接繁忙或關閉,那麼就會使用其餘的鏈接

⚠️Tx和Stmt不能分離,意思就是Tx必須調用本身的Tx.Prepare()函數來生成Stmt來供本身使用,而不能使用DB生成的Stmt,由於這樣他們使用的一定不是同一個鏈接。

固然,若是你想要在該事務中使用已存在的狀態,參見Tx.Stmt方法,將DB的Stmt轉成Tx的Stmt。

type Tx

type Tx struct { // 內含隱藏或非導出字段
}

Tx表明一個進行中的數據庫事務。

一次事務必須以對Commit或Rollback的調用結束。

調用Commit或Rollback後,全部對事務的操做都會失敗並返回錯誤值ErrTxDone。

func (*Tx) Exec

func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)

Exec執行命令,但不返回結果。例如執行insert和update。

func (*Tx) Query

func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)

Query執行查詢並返回零到多行結果(Rows),通常執行select命令。

func (*Tx) QueryRow

func (tx *Tx) QueryRow(query string, args ...interface{}) *Row

QueryRow執行查詢並指望返回最多一行結果(Row)。QueryRow老是返回非nil的結果,查詢失敗的錯誤會延遲到在調用該結果的Scan方法時釋放。

func (*Tx) Prepare

func (tx *Tx) Prepare(query string) (*Stmt, error)

Prepare準備一個專用於該事務的狀態。返回的該事務專屬狀態操做在Tx遞交或回滾後不能再使用,所以必定要在事務結束前,即調用Commit()或Rollback函數前關閉準備語句

在事務中使用defer stmt.Close()是至關危險的。由於當事務Stmt結束後,它會先釋放本身持有的數據庫DB鏈接,但事務Tx建立的未關閉Stmt仍然保留着對事務Tx鏈接的引用

在事務結束後執行stmt.Close(),他就會根據引用去查找以前的數據庫DB鏈接,而後想要釋放它。可是其實數據庫的鏈接早就被釋放了,並且若是原來釋放的數據庫DB鏈接已經被其餘查詢獲取並使用,就會產生競爭,極有可能破壞鏈接的狀態。所以二者的釋放順序是十分重要的

舉例:

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func checkErr(err error){ if err != nil{ log.Fatal(err) } } func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() checkErr(err) //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } tx, err := db.Begin() checkErr(err) defer tx.Commit() stmt1, err := tx.Prepare("insert userinfo set username =?,department=?,created=?") checkErr(err) result, err := stmt1.Exec("testTx", "PD", "2019-02-21") checkErr(err) id, err := result.LastInsertId() checkErr(err) defer stmt1.Close() stmt2, err := tx.Prepare("select * from userinfo where uid =?") checkErr(err) //查詢數據
    var uid int 
    var username, department, created string err = stmt2.QueryRow(id).Scan(&uid, &username, &department, &created) checkErr(err) fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created) defer stmt2.Close() }

上面的defer會安裝stmt2 -> stmt1 -> tx -> db的順序來關閉鏈接

成功返回:

userdeMBP:go-learning user$ go run test.go Uid is 6, username is testTx, department is PD, created at 2019-02-21

若是將tx.Commit()寫在stmt.Close()以前,則會出錯,舉例:

package main import( "fmt"
    "log"
    "database/sql" _ "github.com/go-sql-driver/mysql" ) func checkErr(err error){ if err != nil{ log.Fatal(err) } } func main() { db, err := sql.Open("mysql", "root:user78@/test") //後面格式爲"user:password@/dbname"
 defer db.Close() checkErr(err) //使用Ping檢查數據庫是否實際可用
    if err = db.Ping(); err != nil{ log.Fatal(err) } tx, err := db.Begin() checkErr(err) tx.Commit() stmt, err := tx.Prepare("select * from userinfo where uid =?") checkErr(err) //查詢數據
    var uid int 
    var username, department, created string err = stmt.QueryRow(6).Scan(&uid, &username, &department, &created) checkErr(err) fmt.Printf("Uid is %v, username is %s, department is %s, created at %s\n", uid, username, department, created) err = tx.Commit() checkErr(err) defer stmt.Close() }

返回:

userdeMBP:go-learning user$ go run test.go 2019/02/21 15:58:00 sql: transaction has already been committed or rolled back exit status 1

 

func (*Tx) Stmt

func (tx *Tx) Stmt(stmt *Stmt) *Stmt

Stmt使用已存在的狀態生成一個該事務特定的狀態。

示例:

updateMoney, err := db.Prepare("UPDATE balance SET money=money+? WHERE id=?") ... tx, err := db.Begin() ... res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)

func (*Tx) Commit

func (tx *Tx) Commit() error

Commit遞交事務。

func (*Tx) Rollback

func (tx *Tx) Rollback() error

Rollback放棄並回滾事務。

相關文章
相關標籤/搜索