Golang中MYSQL驅動
- Mysql庫https://github.com/go-sql-driver/mysql
- Go本身不提供具體數據庫驅動,只提供驅動接口和管理。
- 各個數據庫驅動需要第三方實現,並且注冊到Go中的驅動管理中。
安裝golang mysql drvier
go get github.com/go-sql-driver/mysql
代碼中需要注冊mysql數據庫驅動,通過引入空白導入mysql包來完成。
為什么需要使用空白導入? 是因為需要執行mysql包的初始化代碼(代碼位於%GOPATH%/github.com/go-sql-driver/mysql/driver.go)
func init() { sql.Register("mysql", &MySQLDriver{}) }
連接數據的DSN格式
username:password@protocol(address)/dbname?param=value
Prepared Statement
sql.Stmt支持預備表達式,可以用來優化SQL查詢提高性能,減少SQL注入的風險, DB.Prepare()和Tx.Prepare()都提供了對於預備表達式的支持。
預處理的流程:
step1. 將sql分為2部分.命令部分和數據部分.
step2. 首先將命令部分發送給mysql服務器,mysql進行預處理.(如生成AST)
step3. 然后將數據部分發送給mysql服務器,mysql進行占位符替換.
step4. mysql服務器執行sql語句,把執行結果發送給客戶端.
預處理的優勢:
1.因為發送命令后,在mysql服務器端,就會將AST生成好,所以不需要對每一次值的更換都重新生成一次AST.對同樣的數據不同的SQL來講,只需生成1次AST,並緩存起來即可.
2.避免SQL注入.因為mysql知道再次發送過來的內容為”數據”,因此不會將這些數據解析為SQL,避免了SQL注入.
需要注意的點:
使用預處理進行查詢操作時,不僅在defer時需要關閉結果集,而且還要關閉命令句柄,否則同樣會占用連接,導致阻塞.

package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" ) type User struct { Id int `db:"id"` Name string `db:"name"` Age int `db:"age"` } func PrepareQuery(db *sql.DB, id int) { stmt, err := db.Prepare("select id, name, age from user where id>?") if err != nil { panic(err) } rows, err := stmt.Query(id) if err != nil { panic(err) } defer stmt.Close() defer rows.Close() for rows.Next(){ var user User err := rows.Scan(&user.Id, &user.Name, &user.Age) if err != nil { panic(err) } fmt.Printf("user: %#v\n", user) } } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } defer db.Close() PrepareQuery(db, 0) }
Mysql創建表:
CREATE TABLE user (
id int(20) NOT NULL AUTO_INCREMENT,
name varchar(20) DEFAULT '',
age int(2) DEFAULT '0',
PRIMARY KEY (id))
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
數據庫增刪改查
insert

package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" ) func Insert(db *sql.DB) { name := "Vincent" age := 18 result, err := db.Exec("insert into user(name, age) values (?,?)", name, age) if err != nil { panic(err) } id, err := result.LastInsertId() if err != nil { panic(err) } affected, err := result.RowsAffected() if err != nil { panic(err) } fmt.Printf("last insert id:%d affect rows:%d\n", id, affected) } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } err = db.Ping() if err != nil { panic(err) } fmt.Println("connect to db success!!!") Insert(db) }
delete

package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" ) func Delete(db *sql.DB, id int) { result, err := db.Exec("delete from user where id=?", id) if err != nil { panic(err) } rowsAffected, err := result.RowsAffected() if err != nil { panic(err) } fmt.Printf("delect id:%d, affect rows:%d\n", id, rowsAffected) } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, _ := sql.Open("mysql", dns) Delete(db, 2) }
update

package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" ) func Update(db *sql.DB) { name := "Miles" age := 88 id := 3 result, err := db.Exec("update user set name=?, age=? where id=?", name, age, id) if err != nil { panic(err) } // RowsAffected returns the number of rows affected by an // update, insert, or delete. rowsAffected, err := result.RowsAffected() if err != nil { panic(err) } fmt.Printf("update id:%d, affect rows:%d\n", id, rowsAffected) } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } err = db.Ping() if err != nil { panic(err) } fmt.Println("connect to db success!!!") Update(db) }
query

package main import ( "database/sql" _ "github.com/go-sql-driver/mysql" "fmt" "log" ) type User struct { Id int `db:"id"` Name string `db:"name"` Age int `db:"age"` } // 單行查詢,如果查詢到多個結果,只返回第一行,查詢不到結果就ErrNoRows錯誤。 func QueryRow(db *sql.DB) { id:= 2 row := db.QueryRow("select id, name, age from user where id=?", id) var user User err := row.Scan(&user.Id, &user.Name, &user.Age) if err == sql.ErrNoRows { log.Printf("not found data of the id:%d",id) } if err != nil { panic(err) } fmt.Printf("user: %#v\n", user) } // 多行查詢, 查詢不到任何記錄也不會報錯。 func Query(db *sql.DB) { id := 0 rows, err := db.Query("select id, name, age from user where id>?", id) if err != nil { panic(err) } if err == sql.ErrNoRows { log.Printf("not found data of id:%d\n", id) return } defer rows.Close() for rows.Next() { var user User err := rows.Scan(&user.Id, &user.Name, &user.Age) if err != nil { panic(err) } fmt.Printf("user: %#v\n", user) } } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } err = db.Ping() if err != nil { panic(err) } fmt.Printf("connect to db success\n") //QueryRow(db) Query(db) }
事務支持
事務(transaction)
- transaction, err := Db.Begin() 開啟事務
- transaction.Exec() 執行事務
- transaction.Commit() 提交事務
- transaction.Rollback() 回滾事務
A. 事務的應用場景
1. 同時更新多個表
2. 同時更新多行數據
B. 事務的ACID
1. 原子性
2. 一致性
3. 隔離性
4. 持久性
需要注意的點:
1. 執行失敗要回滾
2. 提交失敗要回滾

package main import ( _ "github.com/go-sql-driver/mysql" "database/sql" "fmt" ) func Transaction(db *sql.DB) { // 開啟事務 tx, err := db.Begin() if err != nil { panic(err) } result, err := tx.Exec("insert into user(name, age)values(?,?)", "Jack", 98) if err != nil { // 失敗回滾 tx.Rollback() panic(err) } fmt.Println("result", result) exec, err := tx.Exec("update user set name=?, age=? where id=?", "Jack", 98, 1) if err != nil { // 失敗回滾 tx.Rollback() panic(err) } fmt.Println("exec", exec) // 提交事務 err = tx.Commit() if err != nil { // 失敗回滾 tx.Rollback() panic(err) } } func main() { dns := "root:123456@tcp(172.16.65.200:3306)/golang" db, err := sql.Open("mysql", dns) if err != nil { panic(err) } err = db.Ping() if err != nil { panic(err) } Transaction(db) }
Mysql日期時間類型報錯
sql: Scan error on column index 1: unsupported Scan, storing driver.Value type []uint8 into type *time.Time
原因是在調用sql.Open()時沒有將parseTime設置為True。加入parseTime即可修復問題:
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true")