Go語(yǔ)言操作MySQL的知識(shí)總結(jié)
一、連接
Go語(yǔ)言中的database/sql
包提供了保證SQL或類SQL數(shù)據(jù)庫(kù)的泛用接口,并不提供具體的數(shù)據(jù)庫(kù)驅(qū)動(dòng)。使用database/sql
包時(shí)必須注入(至少)一個(gè)數(shù)據(jù)庫(kù)驅(qū)動(dòng)。
我們常用的數(shù)據(jù)庫(kù)基本上都有完整的第三方實(shí)現(xiàn)。例如:MySQL驅(qū)動(dòng)
1.1 下載依賴
go get -u github.com/go-sql-driver/mysql
1.2 使用MySQL驅(qū)動(dòng)
func Open(driverName, dataSourceName string) (*DB, error)
Open打開(kāi)一個(gè)dirverName指定的數(shù)據(jù)庫(kù),dataSourceName指定數(shù)據(jù)源,一般至少包括數(shù)據(jù)庫(kù)文件名和其它連接必要的信息。
import ( "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { dsn := "user:password@tcp(127.0.0.1:3306)/dbname" db, err := sql.Open("mysql", dsn) if err != nil { panic(err) } defer db.Close()
1.3 初始化連接
返回的DB對(duì)象可以安全地被多個(gè)goroutine并發(fā)使用,并且維護(hù)其自己的空閑連接池。因此,Open函數(shù)應(yīng)該僅被調(diào)用一次,很少需要關(guān)閉這個(gè)DB對(duì)象。
接下來(lái),我們定義一個(gè)全局變量db
,用來(lái)保存數(shù)據(jù)庫(kù)連接對(duì)象。將上面的示例代碼拆分出一個(gè)獨(dú)立的initDB
函數(shù),只需要在程序啟動(dòng)時(shí)調(diào)用一次該函數(shù)完成全局變量db的初始化,其他函數(shù)中就可以直接使用全局變量db
了。
// 定義一個(gè)全局對(duì)象db var db *sql.DB // 定義一個(gè)初始化數(shù)據(jù)庫(kù)的函數(shù) func initDB() (err error) { // DSN:Data Source Name dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True" // 注意?。?!這里不要使用:=,我們是給全局變量賦值,然后在main函數(shù)中使用全局變量db db, err = sql.Open("mysql", dsn) if err != nil { return err } // 嘗試與數(shù)據(jù)庫(kù)建立連接 err = db.Ping() if err != nil { return err } return nil } func main() { err := initDB() if err != nil { fmt.Printf("init db failed,err:%v\n", err) return } }
其中sql.DB
是表示連接的數(shù)據(jù)庫(kù)對(duì)象(結(jié)構(gòu)體實(shí)例),它保存了連接數(shù)據(jù)庫(kù)相關(guān)的所有信息。它內(nèi)部維護(hù)著一個(gè)具有零到多個(gè)底層連接的連接池,它可以安全地被多個(gè)goroutine同時(shí)使用。
1.4 SetMaxOpenConns
func (db *DB) SetMaxOpenConns(n int)
SetMaxOpenConns
設(shè)置與數(shù)據(jù)庫(kù)建立連接的最大數(shù)目。 如果n大于0且小于最大閑置連接數(shù),會(huì)將最大閑置連接數(shù)減小到匹配最大開(kāi)啟連接數(shù)的限制。 如果n<=0,不會(huì)限制最大開(kāi)啟連接數(shù),默認(rèn)為0(無(wú)限制)。
1.5 SetMaxIdleConns
func (db *DB) SetMaxIdleConns(n int)
SetMaxIdleConns設(shè)置連接池中的最大閑置連接數(shù)。 如果n大于最大開(kāi)啟連接數(shù),則新的最大閑置連接數(shù)會(huì)減小到匹配最大開(kāi)啟連接數(shù)的限制。 如果n<=0,不會(huì)保留閑置連接。
二、CRUD
2.1 建庫(kù)建表
我們先在MySQL中創(chuàng)建一個(gè)名為sql_test
的數(shù)據(jù)庫(kù)
CREATE DATABASE sql_test;
進(jìn)入該數(shù)據(jù)庫(kù):
use sql_test;
執(zhí)行以下命令創(chuàng)建一張用于測(cè)試的數(shù)據(jù)表:
CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) DEFAULT '', `age` INT(11) DEFAULT '0', PRIMARY KEY(`id`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
2.2 查詢
為了方便查詢,我們事先定義好一個(gè)結(jié)構(gòu)體來(lái)存儲(chǔ)user表的數(shù)據(jù)。
type user struct { id int age int name string }
2.3 單行查詢
單行查詢db.QueryRow()
執(zhí)行一次查詢,并期望返回最多一行結(jié)果(即Row)。QueryRow總是返回非nil的值,直到返回值的Scan方法被調(diào)用時(shí),才會(huì)返回被延遲的錯(cuò)誤。
func (db *DB) QueryRow(query string, args ...interface{}) *Row
具體示例代碼:
// 查詢單條數(shù)據(jù)示例 func queryRowDemo() { sqlStr := "select id, name, age from user where id=?" var u user // 確保QueryRow之后調(diào)用Scan方法,否則持有的數(shù)據(jù)庫(kù)鏈接不會(huì)被釋放 err := db.QueryRow(sqlStr, 1).Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) }
2.4 多行查詢
多行查詢db.Query()
執(zhí)行一次查詢,返回多行結(jié)果(即Rows),一般用于執(zhí)行select命令。參數(shù)args表示query中的占位參數(shù)。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
具體示例代碼:
// 查詢多條數(shù)據(jù)示例 func queryMultiRowDemo() { sqlStr := "select id, name, age from user where id > ?" rows, err := db.Query(sqlStr, 0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } // 關(guān)閉rows釋放持有的數(shù)據(jù)庫(kù)鏈接 defer rows.Close() // 循環(huán)讀取結(jié)果集中的數(shù)據(jù) for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } }
2.5 插入數(shù)據(jù)
插入、更新和刪除操作都使用Exec
方法。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Exec執(zhí)行一次命令(包括查詢、刪除、更新、插入等),返回的Result是對(duì)已執(zhí)行的SQL命令的總結(jié)。參數(shù)args表示query中的占位參數(shù)。
具體插入數(shù)據(jù)示例代碼如下:
// 插入數(shù)據(jù) func insertRowDemo() { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "王五", 38) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } theID, err := ret.LastInsertId() // 新插入數(shù)據(jù)的id if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n", err) return } fmt.Printf("insert success, the id is %d.\n", theID) }
2.6 更新數(shù)據(jù)
具體更新數(shù)據(jù)示例代碼如下:
// 更新數(shù)據(jù) func updateRowDemo() { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39, 3) if err != nil { fmt.Printf("update failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數(shù) if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) }
2.7 刪除數(shù)據(jù)
具體刪除數(shù)據(jù)的示例代碼如下:
// 刪除數(shù)據(jù) func deleteRowDemo() { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 3) if err != nil { fmt.Printf("delete failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數(shù) if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) }
三、事務(wù)
3.1 什么是事務(wù)
事務(wù):一個(gè)最小的不可再分的工作單元;通常一個(gè)事務(wù)對(duì)應(yīng)一個(gè)完整的業(yè)務(wù)(例如銀行賬戶轉(zhuǎn)賬業(yè)務(wù),該業(yè)務(wù)就是一個(gè)最小的工作單元),同時(shí)這個(gè)完整的業(yè)務(wù)需要執(zhí)行多次的DML(insert、update、delete)語(yǔ)句共同聯(lián)合完成。A轉(zhuǎn)賬給B,這里面就需要執(zhí)行兩次update操作。
在MySQL中只有使用了Innodb
數(shù)據(jù)庫(kù)引擎的數(shù)據(jù)庫(kù)或表才支持事務(wù)。事務(wù)處理可以用來(lái)維護(hù)數(shù)據(jù)庫(kù)的完整性,保證成批的SQL語(yǔ)句要么全部執(zhí)行,要么全部不執(zhí)行。
3.2 事務(wù)的ACID
通常事務(wù)必須滿足4個(gè)條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨(dú)立性)、持久性(Durability)。
條件 | 解釋 |
---|---|
原子性 | 一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開(kāi)始前的狀態(tài),就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣。 |
一致性 | 在事務(wù)開(kāi)始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫(kù)的完整性沒(méi)有被破壞。這表示寫(xiě)入的資料必須完全符合所有的預(yù)設(shè)規(guī)則,這包含資料的精確度、串聯(lián)性以及后續(xù)數(shù)據(jù)庫(kù)可以自發(fā)性地完成預(yù)定的工作。 |
隔離性 | 數(shù)據(jù)庫(kù)允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫(xiě)和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。事務(wù)隔離分為不同級(jí)別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復(fù)讀(repeatable read)和串行化(Serializable)。 |
持久性 | 事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。 |
3.3 事務(wù)相關(guān)方法
Go語(yǔ)言中使用以下三個(gè)方法實(shí)現(xiàn)MySQL中的事務(wù)操作。 開(kāi)始事務(wù)
func (db *DB) Begin() (*Tx, error)
提交事務(wù)
func (tx *Tx) Commit() error
回滾事務(wù)
func (tx *Tx) Rollback() error
3.4 事務(wù)示例
下面的代碼演示了一個(gè)簡(jiǎn)單的事務(wù)操作,該事物操作能夠確保兩次更新操作要么同時(shí)成功要么同時(shí)失敗,不會(huì)存在中間狀態(tài)。
// 事務(wù)操作示例 func transactionDemo() { tx, err := db.Begin() // 開(kāi)啟事務(wù) if err != nil { if tx != nil { tx.Rollback() // 回滾 } fmt.Printf("begin trans failed, err:%v\n", err) return } sqlStr1 := "Update user set age=30 where id=?" ret1, err := tx.Exec(sqlStr1, 2) if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec sql1 failed, err:%v\n", err) return } affRow1, err := ret1.RowsAffected() if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err) return } sqlStr2 := "Update user set age=40 where id=?" ret2, err := tx.Exec(sqlStr2, 3) if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec sql2 failed, err:%v\n", err) return } affRow2, err := ret2.RowsAffected() if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err) return } fmt.Println(affRow1, affRow2) if affRow1 == 1 && affRow2 == 1 { fmt.Println("事務(wù)提交啦...") tx.Commit() // 提交事務(wù) } else { tx.Rollback() fmt.Println("事務(wù)回滾啦...") } fmt.Println("exec trans success!") }
到此這篇關(guān)于Go語(yǔ)言操作MySQL的知識(shí)總結(jié)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言操作MySQL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go 1.21新增的slices包中切片函數(shù)用法詳解
Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過(guò)代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下2023-08-08Go語(yǔ)言中關(guān)閉帶緩沖區(qū)的頻道實(shí)例分析
這篇文章主要介紹了Go語(yǔ)言中關(guān)閉帶緩沖區(qū)的頻道,實(shí)例分析了帶緩沖區(qū)頻道的原理與用法,以及關(guān)閉帶緩沖區(qū)頻道的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02golang包循環(huán)引用的幾種解決方案總結(jié)
golang有包循環(huán)引用問(wèn)題,用過(guò)的應(yīng)該都知道,下面這篇文章主要給大家介紹了關(guān)于golang包循環(huán)引用的幾種解決方案,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09Go外部依賴包從vendor,$GOPATH和$GOPATH/pkg/mod查找順序
這篇文章主要介紹了Go外部依賴包vendor,$GOPATH和$GOPATH/pkg/mod下查找順序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12