golang的database.sql包和事務(wù)處理操作步驟
事務(wù)(Transaction)
事務(wù)是數(shù)據(jù)庫操作中的一個邏輯單元,由一系列的數(shù)據(jù)庫操作組成。這一系列操作要么全部執(zhí)行并且提交,要么全部回滾,確保數(shù)據(jù)的一致性和完整性。事務(wù)具有以下四個主要特性,通常被稱為ACID特性:
- 原子性(Atomicity):事務(wù)作為一個整體,所有的操作要么全部執(zhí)行,要么全部不執(zhí)行,不會出現(xiàn)部分執(zhí)行的情況。
- 一致性(Consistency):在事務(wù)開始和結(jié)束時,數(shù)據(jù)庫的狀態(tài)都是一致的,符合預(yù)定的約束條件。
- 隔離性(Isolation):一個事務(wù)的執(zhí)行不影響另一個事務(wù)的執(zhí)行,除非另一個事務(wù)等待第一個事務(wù)完成。
- 持久性(Durability):一旦事務(wù)提交,所做的修改就會被永久保存在數(shù)據(jù)庫中,不會因系統(tǒng)故障或崩潰而丟失。
在Go語言中,使用database/sql
包進行事務(wù)的基本步驟如下:
- 開始事務(wù):使用
db.Begin()
方法啟動一個事務(wù),返回一個事務(wù)對象tx
。 - 執(zhí)行SQL操作:使用事務(wù)對象
tx
執(zhí)行多個SQL操作,如tx.Exec()
、tx.Query()
等。 - 提交或回滾事務(wù):如果所有SQL操作都成功執(zhí)行,使用
tx.Commit()
提交事務(wù);如果出現(xiàn)錯誤,使用tx.Rollback()
回滾事務(wù)。
事務(wù)的使用場景
- 轉(zhuǎn)賬操作:轉(zhuǎn)賬操作涉及從一個賬戶扣款和另一個賬戶加款,這兩個操作必須同時成功,否則數(shù)據(jù)會不一致。
- 訂單處理:在提交訂單時,可能需要減少庫存并增加訂單記錄,這些操作需要保證同時成功。
- 批量操作:在批量插入、更新或刪除數(shù)據(jù)時,確保所有操作要么全部執(zhí)行,要么全部回滾。
注意事項
- 盡量減少事務(wù)的粒度:長時間的事務(wù)會占用數(shù)據(jù)庫資源,可能導致其他操作被阻塞。盡量確保事務(wù)中的操作只涉及必要的數(shù)據(jù)庫訪問。
- 避免在事務(wù)中進行長時間的操作:如果在事務(wù)中進行文件IO、網(wǎng)絡(luò)請求等長時間操作,可能會導致數(shù)據(jù)庫連接被長時間占用,影響系統(tǒng)性能。
- 正確處理事務(wù)的提交和回滾:確保在所有可能的錯誤情況下,事務(wù)能夠正確回滾,并釋放相關(guān)資源。
數(shù)據(jù)庫連接方法
dsn := "username:password@tcp(127.0.0.1:3306)/dbname"
方法名 | 描述 | 示例 |
---|---|---|
sql.Open() | 打開數(shù)據(jù)庫連接 | db, err := sql.Open("mysql", dsn) |
db.Ping() | 測試數(shù)據(jù)庫連接是否有效 | err = db.Ping() |
db.Close() | 關(guān)閉數(shù)據(jù)庫連接 | defer db.Close() |
事務(wù)方法
方法名 | 描述 | 示例 |
---|---|---|
db.Begin() | 開始一個事務(wù) | tx, err := db.Begin() |
tx.Rollback() | 回滾事務(wù) | tx.Rollback() |
tx.Commit() | 提交事務(wù) | err = tx.Commit() |
查詢和執(zhí)行方法
方法名 | 描述 | 示例 |
---|---|---|
tx.Exec() | 執(zhí)行不返回結(jié)果的SQL語句,用于CREATE、INSERT、UPDATE、DELETE等操作 | tx.Exec("create table ...") |
tx.Query() | 執(zhí)行返回多行結(jié)果的SQL查詢 | rows, err := tx.Query("select ...") |
tx.QueryRow() | 執(zhí)行返回單行結(jié)果的SQL查詢 | tx.QueryRow("select ...") |
stmt.Exec() | 使用預(yù)處理語句執(zhí)行SQL語句 | stmt.Exec("f", "g") |
預(yù)處理語句
方法名 | 描述 | 示例 |
---|---|---|
tx.Prepare() | 創(chuàng)建預(yù)處理語句 | stmt, err := tx.Prepare(...) |
stmt.Close() | 關(guān)閉預(yù)處理語句 | defer stmt.Close() |
查詢結(jié)果處理
方法名 | 描述 | 示例 |
---|---|---|
rows.Next() | 逐行迭代查詢結(jié)果 | rows.Next() |
rows.Scan() | 將當前行的列值賦值給變量 | rows.Scan(&s1, &s2) |
rows.Err() | 檢查查詢和迭代過程中的錯誤 | rows.Err() |
rows.Close() | 關(guān)閉結(jié)果集,釋放相關(guān)資源 | defer rows.Close() |
預(yù)處理語句(Prepared Statements)
預(yù)處理語句是指在數(shù)據(jù)庫中提前編譯和優(yōu)化的SQL語句模板,可以在之后多次重復使用。預(yù)處理語句的主要優(yōu)點如下:
- 提高效率:數(shù)據(jù)庫可以提前編譯和優(yōu)化預(yù)處理語句,減少每次執(zhí)行SQL時的解析時間,特別是在需要多次執(zhí)行相同SQL語句時。
- 防止SQL注入:通過參數(shù)化的SQL語句,用戶輸入的數(shù)據(jù)不會直接嵌入到SQL語句中,降低了SQL注入的風險。
- 減少網(wǎng)絡(luò)開銷:在需要多次執(zhí)行相同的SQL語句時,客戶端只需要發(fā)送參數(shù),不需要每次都發(fā)送完整的SQL語句,減少網(wǎng)絡(luò)通信的數(shù)據(jù)量。
在Go語言中,使用預(yù)處理語句的基本步驟如下:
- 準備預(yù)處理語句:使用
tx.Prepare()
方法創(chuàng)建一個預(yù)處理語句對象stmt
。 - 執(zhí)行預(yù)處理語句:使用
stmt.Exec()
方法執(zhí)行預(yù)處理語句,傳遞參數(shù)。 - 關(guān)閉預(yù)處理語句:執(zhí)行完畢后,使用
stmt.Close()
方法釋放相關(guān)資源。
以下是一個使用預(yù)處理語句的示例:
stmt, err := tx.Prepare("INSERT INTO table1 (column1, column2) VALUES(?, ?)") if err != nil { fmt.Printf("準備預(yù)處理語句失敗:%v\n", err) return } defer stmt.Close() // 第一次執(zhí)行 _, err = stmt.Exec("f", "g") if err != nil { tx.Rollback() fmt.Printf("執(zhí)行預(yù)處理語句第一次失?。?v\n", err) return } // 第二次執(zhí)行 _, err = stmt.Exec("h", "i") if err != nil { tx.Rollback() fmt.Printf("執(zhí)行預(yù)處理語句第二次失?。?v\n", err) return }
在這個示例中,預(yù)處理語句一次創(chuàng)建,多次執(zhí)行,提升了效率,并降低了SQL注入的風險。
預(yù)處理語句的使用場景
- 批量插入:在需要插入大量數(shù)據(jù)時,使用預(yù)處理語句可以顯著提高效率。
- 頻繁執(zhí)行相同SQL語句:在需要多次執(zhí)行相同的SQL語句時,使用預(yù)處理語句可以減少數(shù)據(jù)庫的解析開銷,提高執(zhí)行速度。
- 防止SQL注入:在處理用戶輸入的數(shù)據(jù)時,使用預(yù)處理語句可以有效防止SQL注入攻擊。
注意事項
- 及時關(guān)閉預(yù)處理語句:使用完預(yù)處理語句后,記得及時關(guān)閉,釋放數(shù)據(jù)庫資源,避免資源泄漏。
- 正確處理參數(shù):確保傳遞給預(yù)處理語句的參數(shù)類型和數(shù)量與預(yù)處理語句中的占位符相匹配。
- 避免過度使用:雖然預(yù)處理語句有諸多優(yōu)勢,但在不需要多次執(zhí)行同一SQL語句的情況下,創(chuàng)建預(yù)處理語句可能會帶來額外的開銷,影響性能。
總的示例代碼:
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) const ( dsn = "username:password@tcp(localhost:3306)/test" ) func main() { db, err := sql.Open("mysql", dsn) if err != nil { fmt.Printf("打開數(shù)據(jù)庫連接失?。?v\n", err) return } defer db.Close() if err := db.Ping(); err != nil { fmt.Printf("數(shù)據(jù)庫連接不可用:%v\n", err) return } transactionExample(db) } func transactionExample(db *sql.DB) { tx, err := db.Begin() if err != nil { fmt.Printf("開始事務(wù)失?。?v\n", err) return } defer func() { if err != nil { fmt.Println("事務(wù)回滾中...") rollbackErr := tx.Rollback() if rollbackErr != nil && rollbackErr != sql.ErrTxDone { fmt.Printf("事務(wù)回滾失?。?v\n", rollbackErr) } } }() // 創(chuàng)建表 fmt.Println("創(chuàng)建表...") err = createTable(tx) if err != nil { return } // 插入數(shù)據(jù) fmt.Println("插入數(shù)據(jù)到table1...") err = insertData(tx) if err != nil { return } // 更新數(shù)據(jù) fmt.Println("更新table1的值...") err = updateData(tx) if err != nil { return } // 刪除數(shù)據(jù) fmt.Println("刪除數(shù)據(jù)...") err = deleteData(tx) if err != nil { return } // 查詢多行數(shù)據(jù) fmt.Println("查詢多行數(shù)據(jù)...") err = queryMultiRows(tx) if err != nil { return } // 查詢單行數(shù)據(jù) fmt.Println("查詢單行數(shù)據(jù)...") err = querySingleRow(tx) if err != nil { return } // 預(yù)處理語句插入數(shù)據(jù) fmt.Println("使用預(yù)處理語句插入數(shù)據(jù)...") err = insertWithPrepare(tx) if err != nil { return } // 提交事務(wù) fmt.Println("提交事務(wù)...") err = tx.Commit() if err != nil { fmt.Printf("提交事務(wù)失?。?v\n", err) return } fmt.Println("事務(wù)處理成功。") } func createTable(tx *sql.Tx) error { _, err := tx.Exec("create table if not exists table1 (column1 nchar(10), column2 nchar(10))") return err } func insertData(tx *sql.Tx) error { _, err := tx.Exec("insert into table1 (column1, column2) values ('a','b'), ('c','d'), ('e','f')") return err } func updateData(tx *sql.Tx) error { _, err := tx.Exec("update table1 set column1 = 'c' where column1 = 'a'") return err } func deleteData(tx *sql.Tx) error { _, err := tx.Exec("delete from table1 where column1 = 'b'") return err } func queryMultiRows(tx *sql.Tx) error { rows, err := tx.Query("select * from table1") if err != nil { return err } defer rows.Close() for rows.Next() { var s1, s2 string err := rows.Scan(&s1, &s2) if err != nil { return fmt.Errorf("掃描失?。?v", err) } fmt.Printf("table1數(shù)據(jù):%s, %s\n", s1, s2) } if err := rows.Err(); err != nil { return fmt.Errorf("遍歷table1失?。?v", err) } return nil } func querySingleRow(tx *sql.Tx) error { var c string err := tx.QueryRow("select column1 from table1 where column1 = 'e'").Scan(&c) if err != nil { if err == sql.ErrNoRows { fmt.Println("沒有找到匹配的行。") } else { return fmt.Errorf("查詢單行數(shù)據(jù)失敗:%v", err) } return nil } fmt.Printf("單行數(shù)據(jù):%s\n", c) return nil } func insertWithPrepare(tx *sql.Tx) error { stmt, err := tx.Prepare("insert into table1 (column1, column2) values(?, ?)") if err != nil { return fmt.Errorf("準備預(yù)處理語句失?。?v", err) } defer stmt.Close() _, err = stmt.Exec("f", "g") if err != nil { return fmt.Errorf("執(zhí)行預(yù)處理語句失?。?v", err) } return nil }
到此這篇關(guān)于golang的database.sql包和事務(wù)處理操作步驟的文章就介紹到這了,更多相關(guān)golang的database.sql包內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語言區(qū)塊鏈實戰(zhàn)實現(xiàn)簡單的區(qū)塊與區(qū)塊鏈
這篇文章主要為大家介紹了go語言區(qū)塊鏈的實戰(zhàn)學習,來實現(xiàn)簡單的區(qū)塊與區(qū)塊鏈示例過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10