Go操作MySQL
一 連接
Go語言中的database/sql包提供了保證SQL或類SQL資料庫的泛用介面,並不提供具體的資料庫驅動。使用database/sql包時必須注入(至少)一個資料庫驅動。
我們常用的資料庫基本上都有完整的第三方實現。例如:MySQL驅動
1.1 下載依賴
go get -u github.com/go-sql-driver/mysql
1.2 使用MySQL驅動
func Open(driverName, dataSourceName string) (*DB, error)
Open打開一個dirverName指定的資料庫,dataSourceName指定數據源,一般至少包括資料庫文件名和其它連接必要的資訊。
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對象可以安全地被多個goroutine並發使用,並且維護其自己的空閑連接池。因此,Open函數應該僅被調用一次,很少需要關閉這個DB對象。
接下來,我們定義一個全局變數db,用來保存資料庫連接對象。將上面的示例程式碼拆分出一個獨立的initDB函數,只需要在程式啟動時調用一次該函數完成全局變數db的初始化,其他函數中就可以直接使用全局變數db了。
// 定義一個全局對象db
var db *sql.DB
// 定義一個初始化資料庫的函數
func initDB() (err error) {
// DSN:Data Source Name
dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// 注意!!!這裡不要使用:=,我們是給全局變數賦值,然後在main函數中使用全局變數db
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
// 嘗試與資料庫建立連接
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是表示連接的資料庫對象(結構體實例),它保存了連接資料庫相關的所有資訊。它內部維護著一個具有零到多個底層連接的連接池,它可以安全地被多個goroutine同時使用。
1.4 SetMaxOpenConns
func (db *DB) SetMaxOpenConns(n int)
SetMaxOpenConns設置與資料庫建立連接的最大數目。 如果n大於0且小於最大閑置連接數,會將最大閑置連接數減小到匹配最大開啟連接數的限制。 如果n<=0,不會限制最大開啟連接數,默認為0(無限制)。
1.5 SetMaxIdleConns
func (db *DB) SetMaxIdleConns(n int)
SetMaxIdleConns設置連接池中的最大閑置連接數。 如果n大於最大開啟連接數,則新的最大閑置連接數會減小到匹配最大開啟連接數的限制。 如果n<=0,不會保留閑置連接。
二 CRUD
2.1 建庫建表
我們先在MySQL中創建一個名為sql_test的資料庫
CREATE DATABASE sql_test;
進入該資料庫:
use sql_test;
執行以下命令創建一張用於測試的數據表:
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 查詢
為了方便查詢,我們事先定義好一個結構體來存儲user表的數據。
type user struct {
id int
age int
name string
}
2.3 單行查詢
單行查詢db.QueryRow()執行一次查詢,並期望返回最多一行結果(即Row)。QueryRow總是返回非nil的值,直到返回值的Scan方法被調用時,才會返回被延遲的錯誤。
func (db *DB) QueryRow(query string, args ...interface{}) *Row
具體示例程式碼:
// 查詢單條數據示例
func queryRowDemo() {
sqlStr := "select id, name, age from user where id=?"
var u user
// 確保QueryRow之後調用Scan方法,否則持有的資料庫鏈接不會被釋放
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()執行一次查詢,返回多行結果(即Rows),一般用於執行select命令。參數args表示query中的佔位參數。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
具體示例程式碼:
// 查詢多條數據示例
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
}
// 關閉rows釋放持有的資料庫鏈接
defer rows.Close()
// 循環讀取結果集中的數據
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 插入數據
插入、更新和刪除操作都使用Exec方法。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Exec執行一次命令(包括查詢、刪除、更新、插入等),返回的Result是對已執行的SQL命令的總結。參數args表示query中的佔位參數。
具體插入數據示例程式碼如下:
// 插入數據
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() // 新插入數據的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 更新數據
具體更新數據示例程式碼如下:
// 更新數據
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() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("update success, affected rows:%d\n", n)
}
2.7 刪除數據
具體刪除數據的示例程式碼如下:
// 刪除數據
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() // 操作影響的行數
if err != nil {
fmt.Printf("get RowsAffected failed, err:%v\n", err)
return
}
fmt.Printf("delete success, affected rows:%d\n", n)
}
三 事務
3.1 什麼是事務?
事務:一個最小的不可再分的工作單元;通常一個事務對應一個完整的業務(例如銀行賬戶轉賬業務,該業務就是一個最小的工作單元),同時這個完整的業務需要執行多次的DML(insert、update、delete)語句共同聯合完成。A轉賬給B,這裡面就需要執行兩次update操作。
在MySQL中只有使用了Innodb資料庫引擎的資料庫或表才支援事務。事務處理可以用來維護資料庫的完整性,保證成批的SQL語句要麼全部執行,要麼全部不執行。
3.2 事務的ACID
通常事務必須滿足4個條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、持久性(Durability)。
| 條件 | 解釋 |
|---|---|
| 原子性 | 一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。 |
| 一致性 | 在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。 |
| 隔離性 | 資料庫允許多個並發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務並發執行時由於交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串列化(Serializable)。 |
| 持久性 | 事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。 |
3.3 事務相關方法
Go語言中使用以下三個方法實現MySQL中的事務操作。 開始事務
func (db *DB) Begin() (*Tx, error)
提交事務
func (tx *Tx) Commit() error
回滾事務
func (tx *Tx) Rollback() error
3.4 事務示例
下面的程式碼演示了一個簡單的事務操作,該事物操作能夠確保兩次更新操作要麼同時成功要麼同時失敗,不會存在中間狀態。
// 事務操作示例
func transactionDemo() {
tx, err := db.Begin() // 開啟事務
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("事務提交啦...")
tx.Commit() // 提交事務
} else {
tx.Rollback()
fmt.Println("事務回滾啦...")
}
fmt.Println("exec trans success!")
}

