1. 程式人生 > 實用技巧 >go的mysql預處理和mysql事物

go的mysql預處理和mysql事物

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 id=?"
	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 id=?"
	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!")
}