參考:https://studygolang.com/pkgdoc
導入方式:
import "database/sql"
sql包提供了保證SQL或類SQL數據庫的泛用接口。
使用sql包時必須注入(至少)一個數據庫驅動。相關可見go標准庫的學習-database/sql/driver
1.空值
參考:https://yq.aliyun.com/articles/178898?utm_content=m_29337
當用戶認為數據庫中的某一列不會出現空值(即nil)而將該列設置為基本類型,然后在從數據庫中接收寫入數據時如果得到了空值nil,程序就會崩潰。
⚠️空值(即nil)和零值是不同的,Go語言的每一個變量都有着默認零值,當數據的零值沒有意義時,可以用零值來表示空值。
空值的解決辦法有:
1)使用零值
如果數據本身從語義上就不會出現零值,或者根本不區分零值和空值,那么最簡便的方法就是使用零值來表示空值
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放棄並回滾事務。