go的mysql预处理和mysql事物

what is mysql预处理?

普通SQL语句执行过程:
    1.客户端对SQL语句进行占位符替换得到完整的SQL语句。
    2.客户端发送完整SQL语句到MySQL服务端
    3.MySQL服务端执行完整的SQL语句并将结果返回给客户端。
        
预处理执行过程:
    1.把SQL语句分成两部分,命令部分与数据部分。
    2.先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
    3.然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
    4.MySQL服务端执行完整的SQL语句并将结果返回给客户端
        批量的插入、批量的查询,sql语句的变化不大,仅仅是占位符的变化,这时候适合使用预处理
        
好处:
    1.提高性能,一次编译,多次使用
        2.避免sql注入

go的mysql预处理

func (db *DB) Prepare(query string) (*Stmt, error)
Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令
// 预处理查询示例
func prepareQueryDemo() {
        sqlStr := "select id, name, age from user where id > ?"
        stmt, err := db.Prepare(sqlStr)
        if err != nil {
                fmt.Printf("prepare failed, err:%v\n", err)
                return
        }
        defer stmt.Close()
        rows, err := stmt.Query(0)
        if err != nil {
                fmt.Printf("query failed, err:%v\n", err)
                return
        }
        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)
        }
}

// 插入、更新和删除操作的预处理十分类似,这里以插入操作的预处理为例
// 预处理插入示例
func prepareInsertDemo() {
        sqlStr := "insert into user(name, age) values (?,?)"
        stmt, err := db.Prepare(sqlStr)
        if err != nil {
                fmt.Printf("prepare failed, err:%v\n", err)
                return
        }
        defer stmt.Close()
        _, err = stmt.Exec("小王子", 18)
        if err != nil {
                fmt.Printf("insert failed, err:%v\n", err)
                return
        }
        _, err = stmt.Exec("沙河娜扎", 18)
        if err != nil {
                fmt.Printf("insert failed, err:%v\n", err)
                return
        }
        fmt.Println("insert success.")
}

sql注入

请记住:永远不要相信用户的输入

// sql注入示例
func sqlInjectDemo(name string) {
        sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
        fmt.Printf("SQL:%s\n", sqlStr)
        var u user
        err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
        if err != nil {
                fmt.Printf("exec failed, err:%v\n", err)
                return
        }
        fmt.Printf("user:%#v\n", u)
}

sqlInjectDemo("xxx' or 1=1#")  // or 1=1为真,#表示注释后面的内容
sqlInjectDemo("xxx' union select * from user #")  // union 连表查询所有用户数据,#表示注释后面的内容
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")

sql语句占位符

MySQL             ?
PostgreSQL        $1, $2等
SQLite            ? 和$1
Oracle            :name

go语言的mysql的事物

事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)

MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务

通常事务必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)

原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样  
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。   
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。   
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。  

Go语言中使用以下三个方法实现MySQL中的事务操作

// 开始
func (db *DB) Begin() (*Tx, error)
// 提交
func (tx *Tx) Commit() error
// 回滚
func (tx *Tx) Rollback() error

事务示例

// 事务操作示例
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 
        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 
        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!")
}