分享

Go 的 MySQL 预处理、MySQL 事务

 菜籽爱编程 2022-04-27

预处理是什么

在普通 SQL 语句执行过程中,客户端会对 SQL 语句进行占位符替换,从而得到要执行的完整 SQL 语句,客户端再将此 SQL 语句发送到服务端执行,服务端最后把结果返回给客户端。

而预处理,则是将 SQL 语句分为命令部分以及数据部分,客户端先把命令部分发送给服务器,服务器先进行预处理,而后客户端才把数据部分发送给服务器,由服务器对 SQL 语句进行占位符替换并执行,最后将结果返回给客户端。

预处理可以提高服务器的性能,提前让服务器编译,一次编译多次执行,甚至可以避免 SQL 注入问题。

Go 实现 MySQL 预处理

在上一期《Go 操作 MySQL 数据库》中,很多例子都使用了预处理。Go 中的 Prepare() 方法会将 SQL 语句发送给服务器,返回一个准备好的状态用于之后的查询和命令,返回值可以同时执行多个查询和命令:

func (db *DB) Prepare(query string) (*Stmt, error) {
 return db.PrepareContext(context.Background(), query)
}

下面是一个查询操作预处理的例子:

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() {
 // 连接数据库
 db, _ := sql.Open("mysql""root:root@(127.0.0.1:3306)/godb_test?charset=utf8mb4")

 // Ping() 验证与数据库的连接是否仍处于活动状态,并在必要时建立连接
 err := db.Ping()

 if err != nil {
  fmt.Println("Database connection failed")
  return
 }

 // 延迟调用关闭数据库 阻止新的查询
 defer db.Close()

 // 准备 SQL 语句
 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()

 // 读取结果集中的数据
 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 实现 MySQL 事务

Go 语言中使用下面的方法实现 MySQL 事务操作:

// 开启事务方法
func (db *DB) Begin() (*Tx, error) {
 return db.BeginTx(context.Background(), nil)
}

// 回滚事务方法
func (tx *Tx) Rollback() error {
 return tx.rollback(false)
}

// 提交事务方法
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, 01) {
  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
}

下面是一个事务操作的例子,该事务操作确保两次更新操作要么同时成功要么同时失败:

package main

import (
 "database/sql"
 "fmt"

 _ "github.com/go-sql-driver/mysql"
)

func main() {
 // 连接数据库
 db, _ := sql.Open("mysql""root:root@(127.0.0.1:3306)/godb_test?charset=utf8mb4")

 // Ping() 验证与数据库的连接是否仍处于活动状态,并在必要时建立连接
 err := db.Ping()

 if err != nil {
  fmt.Println("Database connection failed")
  return
 }

 // 延迟调用关闭数据库 阻止新的查询
 defer db.Close()

 // 开启事务
 t, err := db.Begin()
 if err != nil {
  if t != nil {
   // 回滚
   t.Rollback()
  }
  fmt.Println("Begin transaction failed")
  return
 }

 // 准备 SQL 语句
 sqlStr := "update acl_user set nick_name = ?, age = ? where id = ? and is_deleted = 0"
 // 执行 SQL 语句
 _, err = t.Exec(sqlStr, "AAA"60102)

 if err != nil {
  // 回滚
  t.Rollback()
  fmt.Println("Exec failed")
  return
 }

 // 准备 SQL 语句
 sqlStr2 := "update acl_user set nick_name = ?, age = ? where id = ? and is_deleted = 0"
 // 执行 SQL 语句
 _, err = t.Exec(sqlStr2, "BBB"50103)

 if err != nil {
  // 回滚
  t.Rollback()
  fmt.Println("Exec failed")
  return
 }

 // 提交事务
 err = t.Commit()

 if err != nil {
  // 回滚
  t.Rollback()
  fmt.Println("Commit failed")
  return
 }

 fmt.Println("Exec transaction Success")

}

👇希望对你有帮助,期待你的关注👇

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多