什么是預處理?
普通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\n", 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\n", 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\n", err) } fmt.Printf("u1:%#v\n", 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\n", err) return } // 如果是插入數據的操作,能夠拿到插入數據的id id, err := ret.LastInsertId() if err != nil { fmt.Printf("get id failed,err:%v\n", 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\n", err) return } n, err := ret.RowsAffected() //RowsAffected 受影響的行數 if err != nil { fmt.Printf("get id failed,err:%v\n", err) return } fmt.Printf("更新了%d行數據\n", 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\n", err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get id failed,err:%v\n", err) return } fmt.Printf("刪除了%d行數據\n", 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\n", 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\n", 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\n", 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\n", err) } fmt.Println("連接數據庫成功!") transactionDemo() }