1-操作MySQL之mysql庫
一 快速使用
Go語言中的database/sql
包提供了保證SQL或類SQL資料庫的介面,並沒有具體的實現。使用database/sql
包時必須使用其它資料庫驅動,如第三方實現:https://github.com/go-sql-driver/mysql
1.1 下載
go get -u github.com/go-sql-driver/mysql
1.2 快速連結
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() { //conn_url := "使用者名稱:密碼@tcp(地址:埠)/資料庫名字" connUrl := "root:123@tcp(127.0.0.1:3306)/lqz" db, err := sql.Open("mysql", connUrl) if err != nil { fmt.Println("連結出錯:",err) } defer db.Close() fmt.Println(db) }
1.3 最佳使用方案
Open函式可能只是驗證其引數格式是否正確,並不建立與資料庫的連線。如果要檢查資料來源的名稱是否真實有效,應該呼叫Ping方法。
返回的DB物件可以安全地被多個goroutine併發使用,並且維護其自己的空閒連線池。因此,Open函式應該僅被呼叫一次,很少需要關閉這個DB物件。
接下來,我們定義一個全域性變數
db
,用來儲存資料庫連線物件。將上面的示例程式碼拆分出一個獨立的initDB
函式,只需要在程式啟動時呼叫一次該函式完成全域性變數db的初始化,其他函式中就可以直接使用全域性變數db
了。
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) var DB *sql.DB func createDB() (err error) { connUrl := "root:123@tcp(127.0.0.1:3306)/lqz?charset=utf8mb4" // 注意此處:不要用 DB, err := ; err作為返回值,已經定定於了,DB是全域性變數 DB, err = sql.Open("mysql", connUrl) if err != nil { return } // 嘗試與資料庫建立連線 err = DB.Ping() if err != nil { return } return nil } func main() { err:=createDB() if err != nil { fmt.Println("獲取連結出錯:",err) } fmt.Println(DB) }
其中sql.DB
表示資料庫連結物件,它儲存了連線資料庫相關的所有資訊。它內部維護著一個具有零到多個底層連線的連線池,它可以安全地被多個goroutine同時使用
1.4 設定連線池
預設情況下,連線池增長無限制,並且只要連線池中沒有可用的空閒連線,就會建立連線。我們可以使用DB.SetMaxOpenConns設定池的最大大小。未使用的連線標記為空閒,如果不需要則關閉。要避免建立和關閉大量連線,可以使用DB.SetMaxIdleConns設定最大空閒連線。
注意:該設定方法golang版本至少為1.2
- DB.SetMaxIdleConns(n int) 設定最大空閒連線數
SetMaxIdleConns設定連線池中的最大閒置連線數。 如果n大於最大開啟連線數,則新的最大閒置連線數會減小到匹配最大開啟連線數的限制。 如果n<=0,不會保留閒置連線
- DB.SetMaxOpenConns(n int) :設定最大開啟的連線數
設定與資料庫建立連線的最大數目。 如果n大於0且小於最大閒置連線數,會將最大閒置連線數減小到匹配最大開啟連線數的限制。 如果n<=0,不會限制最大開啟連線數,預設為0(無限制)
- DB.SetConnMaxIdleTime(time.Second*10) 間隔時間
connUrl := "root:123@tcp(127.0.0.1:3306)/lqz?charset=utf8mb4"
DB, _ = sql.Open("mysql", connUrl)
DB.SetMaxOpenConns(30)
DB.SetMaxIdleConns(15)
二 查詢資料
type Music struct {
Id int
Name string
Year string
SignId int
}
2.1 單行查詢db.QueryRow()
單行查詢db.QueryRow()
執行一次查詢,並返回最多一行結果,QueryRow總是返回非nil的值,直到返回值的Scan方法被呼叫時,才會返回被延遲的錯誤
func queryOne() {
var music Music
//QueryRow後一定要呼叫Scan方法,否則持有的資料庫連結不會被釋放
err:=DB.QueryRow("select * from music where id=?",2).Scan(&music.Id,&music.Name,&music.Year,&music.SignId)
if err != nil {
fmt.Println("查詢出錯:",err)
}
fmt.Println(music)
}
2.2 多行查詢db.Query()
多行查詢db.Query()
執行一次查詢,返回多行結果(即Rows),一般用於執行select命令。引數args表示query中的佔位引數
func queryMulti() {
sqlStr := "select * from music where id > ?"
rows, err := DB.Query(sqlStr, 1)
if err != nil {
fmt.Println("查詢出錯:",err)
return
}
// 關閉rows釋放持有的資料庫連結
defer rows.Close()
// 迴圈讀取結果集中的資料
for rows.Next() {
var m Music
err := rows.Scan(&m.Id, &m.Name, &m.Year,&m.SignId)
if err != nil {
fmt.Println("遍歷出錯",err)
return
}
fmt.Println(m)
}
}
三 插入資料
插入、更新和刪除操作都使用Exec
方法
func insertMusic() {
sqlStr := "insert into music(name, year,sign_id) values (?,?,?)"
ret, err := DB.Exec(sqlStr, "聽媽媽的話", 2010,1)
if err != nil {
fmt.Println("插入出錯",err)
return
}
theID, err := ret.LastInsertId() // 新插入資料的id
if err != nil {
fmt.Println("獲取插入的id錯誤:",err)
return
}
fmt.Println("插入成功,id為:", theID)
}
三 刪除資料
func deleteMusic() {
sqlStr := "delete from music where id = ?"
ret, err := DB.Exec(sqlStr, 1)
if err != nil {
fmt.Println("刪除出錯:",err)
return
}
n, err := ret.RowsAffected() // 操作影響的行數
if err != nil {
fmt.Println("獲取操作影響的行數出錯:",err)
return
}
fmt.Println("刪除成功,影響的行數為:",n)
}
四 更新資料
func updateMusic() {
sqlStr := "update music set name= ? where id = ?"
ret, err := DB.Exec(sqlStr, "南拳媽媽", 4)
if err != nil {
fmt.Println("更新失敗",err)
return
}
n, err := ret.RowsAffected()
if err != nil {
fmt.Println("獲取影響的行數失敗:",err)
return
}
fmt.Println("更新成功,影響行數為:",n)
}
五 MySQL預處理
什麼是預處理?
普通SQL語句執行過程:
- 客戶端對SQL語句進行佔位符替換得到完整的SQL語句。
- 客戶端傳送完整SQL語句到MySQL服務端
- MySQL服務端執行完整的SQL語句並將結果返回給客戶端。
預處理執行過程:
- 把SQL語句分成兩部分,命令部分與資料部分。
- 先把命令部分發送給MySQL服務端,MySQL服務端進行SQL預處理。
- 然後把資料部分發送給MySQL服務端,MySQL服務端對SQL語句進行佔位符替換。
- MySQL服務端執行完整的SQL語句並將結果返回給客戶端。
為什麼要預處理?
- 優化MySQL伺服器重複執行SQL的方法,可以提升伺服器效能,提前讓伺服器編譯,一次編譯多次執行,節省後續編譯的成本。
- 避免SQL注入問題。
Go實現MySQL預處理
database/sql
中使用下面的Prepare
方法來實現預處理操作。
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語句!
這裡我們演示一個自行拼接SQL語句的示例,編寫一個根據name欄位查詢user表的函式如下:
// 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)
}
此時以下輸入字串都可以引發SQL注入問題:
sqlInjectDemo("xxx' or 1=1#")
sqlInjectDemo("xxx' union select * from user #")
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")
Go實現MySQL事務
事務的ACID
通常事務必須滿足4個條件(ACID):原子性(Atomicity,或稱不可分割性)、一致性(Consistency)、隔離性(Isolation,又稱獨立性)、永續性(Durability)。
條件 | 解釋 |
---|---|
原子性 | 一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。 |
一致性 | 在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。 |
隔離性 | 資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。 |
永續性 | 事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。 |
事務相關方法
Db.Begin() 開始事務
Db.Commit() 提交事務
Db.Rollback() 回滾事務
案例
func transactionDemo() {
conn,err:=DB.Begin()
if err != nil {
fmt.Println("開啟事務出錯誤:",err)
return
}
_,err=conn.Exec("insert into music(name,year,sign_id) values (?,?,?)","動物世界","2022",2)
if err!=nil{
fmt.Println("插入出錯:",err)
conn.Rollback()
}
_,err=conn.Exec("insert into music (name,year,sign_id) values (?,?,?)","世上只有媽媽好","2022",3)
if err!=nil{
fmt.Println("插入出錯:",err)
conn.Rollback()
}
conn.Commit()
}