在普通 SQL 語(yǔ)句執(zhí)行過(guò)程中,客戶端會(huì)對(duì) SQL 語(yǔ)句進(jìn)行占位符替換,從而得到要執(zhí)行的完整 SQL 語(yǔ)句,客戶端再將此 SQL 語(yǔ)句發(fā)送到服務(wù)端執(zhí)行,服務(wù)端最后把結(jié)果返回給客戶端。
而預(yù)處理,則是將 SQL 語(yǔ)句分為命令部分以及數(shù)據(jù)部分,客戶端先把命令部分發(fā)送給服務(wù)器,服務(wù)器先進(jìn)行預(yù)處理,而后客戶端才把數(shù)據(jù)部分發(fā)送給服務(wù)器,由服務(wù)器對(duì) SQL 語(yǔ)句進(jìn)行占位符替換并執(zhí)行,最后將結(jié)果返回給客戶端。
預(yù)處理可以提高服務(wù)器的性能,提前讓服務(wù)器編譯,一次編譯多次執(zhí)行,甚至可以避免 SQL 注入問(wèn)題。
Go 實(shí)現(xiàn) MySQL 預(yù)處理
在上一期《Go 操作 MySQL 數(shù)據(jù)庫(kù)》
中,很多例子都使用了預(yù)處理。Go 中的 Prepare()
方法會(huì)將 SQL 語(yǔ)句發(fā)送給服務(wù)器,返回一個(gè)準(zhǔn)備好的狀態(tài)用于之后的查詢和命令,返回值可以同時(shí)執(zhí)行多個(gè)查詢和命令:
func (db *DB) Prepare(query string) (*Stmt, error) {
return db.PrepareContext(context.Background(), query)
}
下面是一個(gè)查詢操作預(yù)處理的例子:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
id int
phone string
nickName string
age int
}
func main() {
// 連接數(shù)據(jù)庫(kù)
db, _ := sql.Open("mysql", "root:root@(127.0.0.1:3306)/godb_test?charset=utf8mb4")
// Ping() 驗(yàn)證與數(shù)據(jù)庫(kù)的連接是否仍處于活動(dòng)狀態(tài),并在必要時(shí)建立連接
err := db.Ping()
if err != nil {
fmt.Println("Database connection failed")
return
}
// 延遲調(diào)用關(guān)閉數(shù)據(jù)庫(kù) 阻止新的查詢
defer db.Close()
// 準(zhǔn)備 SQL 語(yǔ)句
sqlStr := "select id, nick_name, phone, age from acl_user where id > ? and is_deleted = 0"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Println("Prepare failed")
return
}
defer stmt.Close()
rows, err := stmt.Query("100")
if err != nil {
fmt.Println("Query failed")
return
}
defer rows.Close()
// 讀取結(jié)果集中的數(shù)據(jù)
for rows.Next() {
var user User
err := rows.Scan(&user.id, &user.nickName, &user.phone, &user.age)
if err != nil {
fmt.Println("Scan failed")
return
}
fmt.Println(user)
}
}
Go 實(shí)現(xiàn) MySQL 事務(wù)
Go 語(yǔ)言中使用下面的方法實(shí)現(xiàn) MySQL 事務(wù)操作:
// 開(kāi)啟事務(wù)方法
func (db *DB) Begin() (*Tx, error) {
return db.BeginTx(context.Background(), nil)
}
// 回滾事務(wù)方法
func (tx *Tx) Rollback() error {
return tx.rollback(false)
}
// 提交事務(wù)方法
func (tx *Tx) Commit() error {
select {
default:
case <-tx.ctx.Done():
if atomic.LoadInt32(&tx.done) == 1 {
return ErrTxDone
}
return tx.ctx.Err()
}
if !atomic.CompareAndSwapInt32(&tx.done, 0, 1) {
return ErrTxDone
}
tx.cancel()
tx.closemu.Lock()
tx.closemu.Unlock()
var err error
withLock(tx.dc, func() {
err = tx.txi.Commit()
})
if err != driver.ErrBadConn {
tx.closePrepared()
}
tx.close(err)
return err
}
下面是一個(gè)事務(wù)操作的例子,該事務(wù)操作確保兩次更新操作要么同時(shí)成功要么同時(shí)失?。?/p>
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 連接數(shù)據(jù)庫(kù)
db, _ := sql.Open("mysql", "root:root@(127.0.0.1:3306)/godb_test?charset=utf8mb4")
// Ping() 驗(yàn)證與數(shù)據(jù)庫(kù)的連接是否仍處于活動(dòng)狀態(tài),并在必要時(shí)建立連接
err := db.Ping()
if err != nil {
fmt.Println("Database connection failed")
return
}
// 延遲調(diào)用關(guān)閉數(shù)據(jù)庫(kù) 阻止新的查詢
defer db.Close()
// 開(kāi)啟事務(wù)
t, err := db.Begin()
if err != nil {
if t != nil {
// 回滾
t.Rollback()
}
fmt.Println("Begin transaction failed")
return
}
// 準(zhǔn)備 SQL 語(yǔ)句
sqlStr := "update acl_user set nick_name = ?, age = ? where id = ? and is_deleted = 0"
// 執(zhí)行 SQL 語(yǔ)句
_, err = t.Exec(sqlStr, "AAA", 60, 102)
if err != nil {
// 回滾
t.Rollback()
fmt.Println("Exec failed")
return
}
// 準(zhǔn)備 SQL 語(yǔ)句
sqlStr2 := "update acl_user set nick_name = ?, age = ? where id = ? and is_deleted = 0"
// 執(zhí)行 SQL 語(yǔ)句
_, err = t.Exec(sqlStr2, "BBB", 50, 103)
if err != nil {
// 回滾
t.Rollback()
fmt.Println("Exec failed")
return
}
// 提交事務(wù)
err = t.Commit()
if err != nil {
// 回滾
t.Rollback()
fmt.Println("Commit failed")
return
}
fmt.Println("Exec transaction Success")
}