《Go語(yǔ)言四十二章經(jīng)》第三十九章 MySql數(shù)據(jù)庫(kù)
作者:李驍
39.1 database/sql包
Go 提供了database/sql包用于對(duì)關(guān)系型數(shù)據(jù)庫(kù)的訪問(wèn)切距,作為操作數(shù)據(jù)庫(kù)的入口對(duì)象sql.DB朽缎,主要為我們提供了兩個(gè)重要的功能:
- sql.DB 通過(guò)數(shù)據(jù)庫(kù)驅(qū)動(dòng)為我們提供管理底層數(shù)據(jù)庫(kù)連接的打開(kāi)和關(guān)閉操作.
- sql.DB 為我們管理數(shù)據(jù)庫(kù)連接池
需要注意的是,sql.DB表示操作數(shù)據(jù)庫(kù)的抽象訪問(wèn)接口, 而非一個(gè)數(shù)據(jù)庫(kù)連接對(duì)象;它可以根據(jù)driver打開(kāi)關(guān)閉數(shù)據(jù)庫(kù)連接谜悟,管理連接池话肖。正在使用的連接被標(biāo)記為繁忙,用完后回到連接池等待下次使用葡幸。所以最筒,如果你沒(méi)有把連接釋放回連接池,會(huì)導(dǎo)致過(guò)多連接使系統(tǒng)資源耗盡礼患。
具體到某一類(lèi)型的關(guān)系型數(shù)據(jù)庫(kù)是钥,需要導(dǎo)入對(duì)應(yīng)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)。下面以MySql8.0為例缅叠,來(lái)講講怎么在Go語(yǔ)言中調(diào)用悄泥。
首先,需要下載第三方包:
go get github.com/go-sql-driver/mysql
在代碼中導(dǎo)入mysql數(shù)據(jù)庫(kù)驅(qū)動(dòng):
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
通常來(lái)說(shuō)肤粱,不應(yīng)該直接使用驅(qū)動(dòng)所提供的方法弹囚,而是應(yīng)該使用 sql.DB,因此在導(dǎo)入 mysql 驅(qū)動(dòng)時(shí)领曼,這里使用了匿名導(dǎo)入的方式(在包路徑前添加 _)鸥鹉,當(dāng)導(dǎo)入了一個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)后,此驅(qū)動(dòng)會(huì)自行初始化并注冊(cè)自己到Go的database/sql上下文中庶骄,因此我們就可以通過(guò) database/sql 包提供的方法訪問(wèn)數(shù)據(jù)庫(kù)了毁渗。
39.2 Mysql數(shù)據(jù)庫(kù)操作
我們先建立表結(jié)構(gòu):
CREATE TABLE t_article_cate (
`cid` int(10) NOT NULL AUTO_INCREMENT,
`cname` varchar(60) NOT NULL,
`ename` varchar(100),
`cateimg` varchar(255),
`addtime` int(10) unsigned NOT NULL DEFAULT '0',
`publishtime` int(10) unsigned NOT NULL DEFAULT '0',
`scope` int(10) unsigned NOT NULL DEFAULT '10000',
`status` tinyint(1) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`cid`),
UNIQUE KEY catename (`cname`)
) ENGINE=InnoDB AUTO_INCREMENT=99 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
由于預(yù)編譯語(yǔ)句(PreparedStatement)提供了諸多好處,可以實(shí)現(xiàn)自定義參數(shù)的查詢(xún)单刁,通常來(lái)說(shuō)灸异,比手動(dòng)拼接字符串 SQL 語(yǔ)句高效,可以防止SQL注入攻擊。
下面代碼使用預(yù)編譯的方式肺樟,來(lái)進(jìn)行增刪改查的操作檐春,并通過(guò)事務(wù)來(lái)批量提交一批數(shù)據(jù)。
在Go語(yǔ)言中對(duì)數(shù)據(jù)類(lèi)型要求很?chē)?yán)格么伯,一般查詢(xún)數(shù)據(jù)時(shí)先定義數(shù)據(jù)類(lèi)型疟暖,但是查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù)存在三種可能:
存在值,存在零值田柔,未賦值NULL 三種狀態(tài)俐巴,因此可以將待查詢(xún)的數(shù)據(jù)類(lèi)型定義為sql.Nullxxx類(lèi)型,可以通過(guò)判斷Valid值來(lái)判斷查詢(xún)到的值是否為賦值狀態(tài)還是未賦值NULL狀態(tài)凯楔。如// sql.NullInt64 sql.NullString
package main
import (
"database/sql"
"fmt"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
)
type DbWorker struct {
Dsn string
Db *sql.DB
}
type Cate struct {
cid int
cname string
addtime int
scope int
}
func main() {
dbw := DbWorker{Dsn: "root:123456@tcp(localhost:3306)/mydb?charset=utf8mb4"}
// 支持下面幾種DSN寫(xiě)法窜骄,具體看mysql服務(wù)端配置,常見(jiàn)為第2種
// 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:ef::ca:fe]:80)/dbname
dbtemp, err := sql.Open("mysql", dbw.Dsn)
dbw.Db = dbtemp
if err != nil {
panic(err)
return
}
defer dbw.Db.Close()
// 插入數(shù)據(jù)測(cè)試
dbw.insertData()
// 刪除數(shù)據(jù)測(cè)試
dbw.deleteData()
// 修改數(shù)據(jù)測(cè)試
dbw.editData()
// 查詢(xún)數(shù)據(jù)測(cè)試
dbw.queryData()
// 事務(wù)操作測(cè)試
dbw.transaction()
}
每次db.Query操作后摆屯,都建議調(diào)用rows.Close()邻遏。 因?yàn)?db.Query() 會(huì)從數(shù)據(jù)庫(kù)連接池中獲取一個(gè)連接,這個(gè)底層連接在結(jié)果集(rows)未關(guān)閉前會(huì)被標(biāo)記為處于繁忙狀態(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)閉颠黎,則此連接會(huì)一直被占用另锋。 因此通常我們使用 defer rows.Close() 來(lái)確保數(shù)據(jù)庫(kù)連接可以正確放回到連接池中。
插入數(shù)據(jù):
// 插入數(shù)據(jù)狭归,sql預(yù)編譯
func (dbw *DbWorker) insertData() {
stmt, _ := dbw.Db.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
defer stmt.Close()
ret, err := stmt.Exec("欄目1", time.Now().Unix(), 10)
// 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)
// RowsAffected和最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id)
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
if LastInsertId, err := ret.LastInsertId(); nil == err {
fmt.Println("LastInsertId:", LastInsertId)
}
if RowsAffected, err := ret.RowsAffected(); nil == err {
fmt.Println("RowsAffected:", RowsAffected)
}
}
刪除數(shù)據(jù):
// 刪除數(shù)據(jù)夭坪,預(yù)編譯
func (dbw *DbWorker) deleteData() {
stmt, err := dbw.Db.Prepare(`DELETE FROM t_article_cate WHERE cid=?`)
ret, err := stmt.Exec(122)
// 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)RowsAffected和
// 最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id).
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
if RowsAffected, err := ret.RowsAffected(); nil == err {
fmt.Println("RowsAffected:", RowsAffected)
}
}
修改數(shù)據(jù):
// 修改數(shù)據(jù),預(yù)編譯
func (dbw *DbWorker) editData() {
stmt, err := dbw.Db.Prepare(`UPDATE t_article_cate SET scope=? WHERE cid=?`)
ret, err := stmt.Exec(111, 123)
// 通過(guò)返回的ret可以進(jìn)一步查詢(xún)本次插入數(shù)據(jù)影響的行數(shù)RowsAffected和
// 最后插入的Id(如果數(shù)據(jù)庫(kù)支持查詢(xún)最后插入Id).
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
if RowsAffected, err := ret.RowsAffected(); nil == err {
fmt.Println("RowsAffected:", RowsAffected)
}
}
查詢(xún)數(shù)據(jù):
// 查詢(xún)數(shù)據(jù)过椎,預(yù)編譯
func (dbw *DbWorker) queryData() {
// 如果方法包含Query室梅,那么這個(gè)方法是用于查詢(xún)并返回rows的。其他用Exec()
// 另外一種寫(xiě)法
// rows, err := db.Query("select id, name from users where id = ?", 1)
stmt, _ := dbw.Db.Prepare(`SELECT cid, cname, addtime, scope From t_article_cate where status=?`)
//err = db.QueryRow("select name from users where id = ?", 1).Scan(&name) // 單行查詢(xún)疚宇,直接處理
defer stmt.Close()
rows, err := stmt.Query(0)
defer rows.Close()
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
// 構(gòu)造scanArgs亡鼠、values兩個(gè)slice,
// scanArgs的每個(gè)值指向values相應(yīng)值的地址
columns, _ := rows.Columns()
fmt.Println(columns)
rowMaps := make([]map[string]string, 9)
values := make([]sql.RawBytes, len(columns))
scans := make([]interface{}, len(columns))
for i := range values {
scans[i] = &values[i]
scans[i] = &values[i]
}
i := 0
for rows.Next() {
//將行數(shù)據(jù)保存到record字典
err = rows.Scan(scans...)
each := make(map[string]string, 4)
// 由于是map引用敷待,放在上層for時(shí)间涵,rowMaps最終返回值是最后一條。
for i, col := range values {
each[columns[i]] = string(col)
}
// 切片追加數(shù)據(jù)榜揖,索引位置有意思浑厚。不這樣寫(xiě)就不是希望的樣子股耽。
rowMaps = append(rowMaps[:i], each)
fmt.Println(each)
i++
}
fmt.Println(rowMaps)
for i, col := range rowMaps {
fmt.Println(i, col)
}
err = rows.Err()
if err != nil {
fmt.Printf(err.Error())
}
}
事務(wù)處理:
db.Begin()開(kāi)始事務(wù)根盒,Commit() 或 Rollback()關(guān)閉事務(wù)钳幅。Tx從連接池中取出一個(gè)連接,在關(guān)閉之前都使用這個(gè)連接炎滞。Tx不能和DB層的BEGIN敢艰,COMMIT混合使用。
func (dbw *DbWorker) transaction() {
tx, err := dbw.Db.Begin()
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
defer tx.Rollback()
stmt, err := tx.Prepare(`INSERT INTO t_article_cate (cname, addtime, scope) VALUES (?, ?, ?)`)
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
for i := 100; i < 110; i++ {
cname := strings.Join([]string{"欄目-", string(i)}, "-")
_, err = stmt.Exec(cname, time.Now().Unix(), i+20)
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
}
err = tx.Commit()
if err != nil {
fmt.Printf("insert data error: %v\n", err)
return
}
stmt.Close()
}
本書(shū)《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在github上同步地址:https://github.com/ffhelicopter/Go42
本書(shū)《Go語(yǔ)言四十二章經(jīng)》內(nèi)容在簡(jiǎn)書(shū)同步地址: http://www.reibang.com/nb/29056963雖然本書(shū)中例子都經(jīng)過(guò)實(shí)際運(yùn)行册赛,但難免出現(xiàn)錯(cuò)誤和不足之處钠导,煩請(qǐng)您指出;如有建議也歡迎交流森瘪。
聯(lián)系郵箱:roteman@163.com