更多個人筆記:(僅供參考,非盈利)
gitee: https://gitee.com/harryhack/it_note
github: https://github.com/ZHLOVEYY/IT_note
本文是基于原生的庫 database/sql進行初步學習
基于ORM等更多操作可以關注我的博客和筆記倉庫
連接 MySQL 和基本 CRUD 操作
需要 go get -u github.com/go-sql-driver/mysql
獲取sql驅動
自己先連接到sql的root數據庫,然后創建/fortest數據庫
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" // 添加 MySQL 驅動。沒有直接使用包的對象所以加_"log"
)func main() {db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/fortest") //這里是user:password@tcp(location)/database的形式,我的密碼是1234if err != nil {log.Fatal("連接失敗", err)}defer db.Close()//測試連接err = db.Ping()if err != nil {log.Fatal("連接失敗", err)}fmt.Println("連接成功")//創建表 注意用if no exists避免重復創建_, err = db.Exec(`CREATE TABLE IF NOT EXISTS gameusers (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50),age INT)`)if err != nil {log.Fatal("創建表失敗:", err)}//插入數據result, err := db.Exec(`INSERT INTO gameusers (name, age) VALUES (?, ?)`, "Alice", 25)if err != nil {log.Fatal(err)}id, _ := result.LastInsertId() //用于獲取最新插入的IDfmt.Printf("插入成功,ID:%d\n", id)//再插入一條_, err = db.Exec(`INSERT INTO gameusers (name, age) VALUES (?, ?)`, "Alice2", 26)if err != nil {log.Fatal(err)}//查詢單條數據var name stringvar age int //方便查詢然后存儲進來err = db.QueryRow("SELECT name, age FROM gameusers WHERE id = ?", id).Scan(&name, &age)if err != nil {log.Fatal(err)}fmt.Printf("查詢結果: name=%s, age=%d\n", name, age)//查詢多條記錄rows, err := db.Query("SELECT * FROM gameusers") //*就是所有列的了// rows, err := db.Query("SELECT id,name,age from gameusers") //顯示指定列if err != nil {log.Fatal("查詢失敗", err)}defer rows.Close()//rows 是一個數據庫結果集,會占用數據庫連接和內存資源,如果不關閉,可能會導致資源泄露for rows.Next() { //類似于迭代器var userID interr := rows.Scan(&userID, &name, &age) //將當前行的數據讀取到指定的變量中if err != nil {log.Fatal("掃描失敗", err)}fmt.Printf("ID:%d ,姓名:%s,年齡:%d\n", userID, name, age)}//更新數據_, err = db.Exec("UPDATE gameusers SET age = ? WHERE id = ?", 26, id)if err != nil {log.Fatal("更新失敗", err)}fmt.Println("更新成功")//刪除數據_, err = db.Exec("DELETE FROM gameusers WHERE id =?", id)if err != nil {log.Fatal("刪除失敗", err)}fmt.Println("刪除成功")}
總結:
- open():用于建立連接
- 需要defer關閉數據庫連接防止浪費資源
- ping():用于測試是否建立連接
- exec():用于執行創建表,CRUD等操作
- QueryRow():用于查詢單條數據
- Query():用于多行查詢
- 注意結合.Next迭代和Scan寫入
- 需要defer關閉防止查詢一直連接浪費資源
事務和結構體映射
事務的特點:
- 原子性:要么全部成功,要么全部失敗
- 一致性:數據庫從一個一致狀態轉換到另一個一致狀態
- 隔離性:事務執行不受其他事務影響
- 持久性:一旦提交,修改就是永久的
例子理解: - 假設你要給 A 轉賬 100 元給 B
- 需要兩個操作:A 減 100,B 加 100
- 如果 A 減 100 成功,但 B 加 100 失敗
- 這時就需要 Rollback,撤銷 A 減 100 的操作
- 確保賬戶金額不會出錯
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" // 添加 MySQL 驅動。沒有直接使用包的對象所以加_"log"
)// User 結構體映射 users 表
type GameUser struct {ID intName stringAge int
}func main() {db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/fortest")if err != nil {log.Fatal("連接失敗", err)}defer db.Close()//測試連接err = db.Ping()if err != nil {log.Fatal("連接失敗", err)}fmt.Println("連接成功")//創建表_, err = db.Exec(`CREATE TABLE IF NOT EXISTS gameusers (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50),age INT)`)if err != nil {log.Fatal("創建表失敗:", err)}// 開啟事務tx, err := db.Begin()if err != nil {log.Fatal("事務開啟失敗", err)}//插入用戶result, err := tx.Exec("INSERT INTO gameusers (name, age) VALUES (?, ?)", "Bob", 30)if err != nil {tx.Rollback()log.Fatal("插入失敗", err)}id, _ := result.LastInsertId()fmt.Println("插入成功,用戶ID:", id)//更新用戶_, err = tx.Exec("UPDATE users SET age = ? WHERE id = ?", 31, id)if err != nil {tx.Rollback()log.Fatal("更新失敗:", err)}// 提交事務err = tx.Commit() //通過commit進行提交if err != nil {log.Fatal("事務提交失敗:", err)}fmt.Println("事務完成")//查詢并映射到結構體var user GameUsererr = db.QueryRow("SELECT id, name, age FROM gameusers WHERE id =?", id).Scan(&user.ID, &user.Name, &user.Age) //將查詢結果映射到user結構體if err != nil {log.Fatal("查詢失敗", err)}fmt.Printf("用戶信息: %+v\n", user)}
執行完會發現報錯:
連接成功
插入成功,用戶ID: 5
2025/04/25 10:12:03 更新失敗:Error 1146 (42S02): Table 'fortest.users' doesn't exist
(這個用戶id我插入很多次,會自己不斷增加的)
這時候檢查數據庫會發現,bob并沒有被插入,這是因為回滾了。那么接下來只要把上面代碼中更新部分的users改為gameusers就可以了
如果不希望總是寫tx.Rollback()
也可以增加:
// 定義一個函數,確保在出錯時回滾事務defer func() {if err != nil {tx.Rollback()return}}()
這樣就會在最后進行統一的判斷
批量插入和預處理語句
預處理可以防sql注入,通過永遠使用 ? 占位符,避免直接拼接 SQL 語句
func main() {db, err := sql.Open("mysql", "root:1234@tcp(127.0.0.1:3306)/fortest")if err != nil {log.Fatal("連接錯誤", err)}defer db.Close()//準備預處理stmt, err := db.Prepare("INSERT INTO gameusers (name,age) values (?,?)")if err != nil {log.Fatal("預處理失敗", err)}defer stmt.Close()//批量插入users := []struct { //定義結構體列表的同時初始化name stringage int}{{"Charlie", 22},{"David", 28},{"Eve", 19},}for _, u := range users {_, err := stmt.Exec(u.name, u.age) //通過Exec傳遞參數進去if err != nil {log.Fatal("批量插入失敗", err)}}fmt.Println("批量插入完成")//查詢所有用戶rows, err := db.Query("SELECT id,name,age FROM gameusers")if err != nil {log.Fatal("查詢失敗", err)}defer rows.Close()for rows.Next() { //讀取數據var id intvar name stringvar age interr = rows.Scan(&id, &name, &age)if err != nil {log.Fatal("讀取是吧", err)}fmt.Printf("id:%d,name:%s,age:%d\n", id, name, age)}
}
還有比如查詢大數據時,限制返回行數(如 LIMIT),以及設置連接數和空閑連接數SetMaxOpenConns、SetMaxIdleConns等等,可以進一步拓展學習