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包進(jìn)行事務(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ù)庫資源,可能導(dǎo)致其他操作被阻塞。盡量確保事務(wù)中的操作只涉及必要的數(shù)據(jù)庫訪問。
- 避免在事務(wù)中進(jìn)行長時間的操作:如果在事務(wù)中進(jìn)行文件IO、網(wǎng)絡(luò)請求等長時間操作,可能會導(dǎo)致數(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() | 將當(dāng)前行的列值賦值給變量 | 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語句模板,可以在之后多次重復(fù)使用。預(yù)處理語句的主要優(yōu)點如下:
- 提高效率:數(shù)據(jù)庫可以提前編譯和優(yōu)化預(yù)處理語句,減少每次執(zhí)行SQL時的解析時間,特別是在需要多次執(zhí)行相同SQL語句時。
- 防止SQL注入:通過參數(shù)化的SQL語句,用戶輸入的數(shù)據(jù)不會直接嵌入到SQL語句中,降低了SQL注入的風(fēng)險。
- 減少網(wǎng)絡(luò)開銷:在需要多次執(zhí)行相同的SQL語句時,客戶端只需要發(fā)送參數(shù),不需要每次都發(fā)送完整的SQL語句,減少網(wǎng)絡(luò)通信的數(shù)據(jù)量。
在Go語言中,使用預(yù)處理語句的基本步驟如下:
- 準(zhǔn)備預(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("準(zhǔn)備預(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注入的風(fēng)險。
預(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("準(zhǔn)備預(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)學(xué)習(xí),來實現(xiàn)簡單的區(qū)塊與區(qū)塊鏈?zhǔn)纠^程,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-10-10

