什么是预处理?
普通SQL语句执行过程:
- 客户端对SQL语句进行占位符替换得到完整的SQL语句。
- 客户端发送完整SQL语句到MySQL服务端
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
- 把SQL语句分成两部分,命令部分与数据部分。
- 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
- 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
- MySQL服务端执行完整的SQL语句并将结果返回给客户端。
为什么要预处理?
- 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
- 避免SQL注入问题。
Go实现MySQL预处理
Go中的
func (db *DB) Prepare(query string) (*Stmt, error)
Prepare
方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" // init() ) // Go连接MySQL示例 var db *sql.DB // db是一个连接池对象,每次操作会从连接池中取一个db对象来服务 func initDB() (err error) { // 数据库信息 // 用户名:密码@tcp(ip:端口)/数据库的名字 dsn := "root:root@tcp(127.0.0.1:3306)/sql_test" // 连接数据库 db, err = sql.Open("mysql", dsn) // 不会校验用户名和密码是否正确 if err != nil { // dsn格式不正确的时候会报错 return } err = db.Ping() // 尝试连接数据库 if err != nil { return } db.SetMaxOpenConns(10) // 设置数据库连接池的最大连接数(连接池中只有10个连接,如果全部被占用,再来请求就会阻塞住) db.SetMaxIdleConns(5) // 设置最大空闲连接数 return } type user struct { id int name string age int } // 查询单个记录 func queryOne(id int) { var u1 user //用来接收查询结果 // 1. 写查询单条记录的sql语句 sqlStr := `select id, name, age from user where id=?;` //?占位 下面的id // 2. 执行并拿到结果 // 必须对rowObj对象调用Scan方法,因为该方法会释放数据库链接 // 从连接池里拿一个连接出来去数据库查询单条记录 db.QueryRow(sqlStr, id).Scan(&u1.id, &u1.name, &u1.age) //&u1.id, &u1.name, &u1.age初始化u1结构体对象(变量) //row一行 // 打印结果 fmt.Printf("u1:%#v ", u1) } // 查询多条 func queryMore(n int) { // 1. SQL语句 sqlStr := `select id, name, age from user where id > ?;` // 2. 执行 rows, err := db.Query(sqlStr, n) if err != nil { fmt.Printf("exec %s query failed, err:%v ", sqlStr, err) return } // 3. 一定要关闭rows,才会释放连接(数据库的连接) defer rows.Close() // 4. 循环取值 for rows.Next() { var u1 user err := rows.Scan(&u1.id, &u1.name, &u1.age) if err != nil { fmt.Printf("scan failed,err:%v ", err) } fmt.Printf("u1:%#v ", u1) } } // 插入数据 func insert() { // 1. 写SQL语句 sqlStr := `insert into user(name, age) values("图朝阳", 28)` // 2. exec ret, err := db.Exec(sqlStr) //exec执行(Python中的exec就是执行字符串代码的,返回值是None,eval有返回值) if err != nil { fmt.Printf("insert failed, err:%v ", err) return } // 如果是插入数据的操作,能够拿到插入数据的id id, err := ret.LastInsertId() if err != nil { fmt.Printf("get id failed,err:%v ", err) return } fmt.Println("id:", id) } // 更新操作 func updateRow(newAge int, id int) { sqlStr := `update user set age=? where id > ?` ret, err := db.Exec(sqlStr, newAge, id) if err != nil { fmt.Printf("update failed, err:%v ", err) return } n, err := ret.RowsAffected() //RowsAffected 受影响的行数 if err != nil { fmt.Printf("get id failed,err:%v ", err) return } fmt.Printf("更新了%d行数据 ", n) } // 删除 func deleteRow(id int) { sqlStr := `delete from user where id=?` ret, err := db.Exec(sqlStr, id) if err != nil { fmt.Printf("delete failed,err:%v ", err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get id failed,err:%v ", err) return } fmt.Printf("删除了%d行数据 ", n) } // 预处理方式插入多条数据 func prepareInsert() { sqlStr := `insert into user(name, age) values(?,?)` stmt, err := db.Prepare(sqlStr) // 1.把SQL语句先发给MySQL预处理一下 if err != nil { fmt.Printf("prepare failed ,err:%v ", err) return } defer stmt.Close() // 后续只需要拿到stmt去执行一些操作 var m = map[string]int{ "六七强": 30, "王相机": 32, "天说": 72, "白慧姐": 40, } for k, v := range m { stmt.Exec(k, v) // 2.后续只需要传值 } } func main() { err := initDB() if err != nil { fmt.Printf("init DB failed, err:%v ", err) } fmt.Println("连接数据库成功!") // queryOne(2) // queryMore(2) // insert() // updateRow(9000, 2) // deleteRow(2) prepareInsert() }
Go实现MySQL事务
什么是事务?
事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。A转账给B,这里面就需要执行两次update操作。
在MySQL中只有使用了Innodb
数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。
事务的ACID
通常事务必须满足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
代码:
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" // init() ) var db *sql.DB // 是一个连接池对象 func initDB() (err error) { // 数据库信息 // 用户名:密码@tcp(ip:端口)/数据库的名字 dsn := "root:root@tcp(127.0.0.1:3306)/sql_test" // 连接数据库 db, err = sql.Open("mysql", dsn) // 不会校验用户名和密码是否正确 if err != nil { // dsn格式不正确的时候会报错 return } err = db.Ping() // 尝试连接数据库 if err != nil { return } db.SetMaxOpenConns(10) // 设置数据库连接池的最大连接数 db.SetMaxIdleConns(5) // 设置最大空闲连接数 return } type user struct { id int name string age int } func transactionDemo() { // 1. 开启事务 tx, err := db.Begin() if err != nil { fmt.Printf("begin failed,err:%v ", err) return } // 执行多个SQL操作 sqlStr1 := `update user set age=age-2 where id=1` sqlStr2 := `update xxx set age=age+2 where id=2` // 执行SQL1 _, err = tx.Exec(sqlStr1) if err != nil { // 要回滚 tx.Rollback() fmt.Println("执行SQL1出错啦,要回滚!") return } // 执行SQL2 _, err = tx.Exec(sqlStr2) if err != nil { // 要回滚 tx.Rollback() fmt.Println("执行SQL2出错啦,要回滚!") return } // 上面两步SQL都执行成功,就提交本次事务 err = tx.Commit() if err != nil { // 要回滚 tx.Rollback() fmt.Println("提交出错啦,要回滚!") return } fmt.Println("事务执行成功!") } func main() { err := initDB() if err != nil { fmt.Printf("init DB failed, err:%v ", err) } fmt.Println("连接数据库成功!") transactionDemo() }