<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
MySQL是業界常用的關係型資料庫,本文介紹了database/sql庫以及Go語言如何操作MySQL資料庫。
_ "github.com/go-sql-driver/mysql"
_ "github.com/lib/pq"
Go語言中的database/sql
包提供了保證SQL或類SQL資料庫的泛用介面,並不提供具體的資料庫驅動。使用database/sql
包時必須注入(至少)一個資料庫驅動。
我們常用的資料庫基本上都有完整的第三方實現。例如:MySQL驅動
func Open(driverName, dataSourceName string) (*DB, error) import ( "database/sql" "fmt" _ "github.com/lib/pq" ) Db, _ := sql.Open("postgres", "postgres://postgres:iLoveShark@127.0.0.1:3432/master?sslmode=disable&fallback_application_name=bikesvc")
Open開啟一個dirverName指定的資料庫,dataSourceName指定資料來源,一般至少包括資料庫檔名和其它連線必要的資訊。
import ( "database/sql" _ "github.com/go-sql-driver/mysql" ) func main() { // DSN:Data Source Name dsn := "user:password@tcp(127.0.0.1:3306)/dbname" db, err := sql.Open("mysql", dsn) if err != nil { panic(err) } defer db.Close() // 注意這行程式碼要寫在上面err判斷的下面 }
Open函數可能只是驗證其引數格式是否正確,實際上並不建立與資料庫的連線。如果要檢查資料來源的名稱是否真實有效,應該呼叫Ping方法。
返回的DB物件可以安全地被多個goroutine並行使用,並且維護其自己的空閒連線池。因此,Open函數應該僅被呼叫一次,很少需要關閉這個DB物件。
// 定義一個全域性物件db var db *sql.DB // 定義一個初始化資料庫的函數 func initDB() (err error) { // DSN:Data Source Name dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True" // 不會校驗賬號密碼是否正確 // 注意!!!這裡不要使用:=,我們是給全域性變數賦值,然後在main函數中使用全域性變數db db, err = sql.Open("mysql", dsn) if err != nil { return err } // 嘗試與資料庫建立連線(校驗dsn是否正確) err = db.Ping() if err != nil { return err } return nil } func main() { err := initDB() // 呼叫輸出化資料庫的函數 if err != nil { fmt.Printf("init db failed,err:%vn", err) return } }
其中sql.DB
是表示連線的資料庫物件(結構體範例),它儲存了連線資料庫相關的所有資訊。它內部維護著一個具有零到多個底層連線的連線池,它可以安全地被多個goroutine同時使用。
func (db *DB) SetMaxOpenConns(n int)
SetMaxOpenConns
設定與資料庫建立連線的最大數目。 如果n大於0且小於最大閒置連線數,會將最大閒置連線數減小到匹配最大開啟連線數的限制。 如果n<=0,不會限制最大開啟連線數,預設為0(無限制)。
func (db *DB) SetMaxIdleConns(n int)
SetMaxIdleConns設定連線池中的最大閒置連線數。 如果n大於最大開啟連線數,則新的最大閒置連線數會減小到匹配最大開啟連線數的限制。 如果n<=0,不會保留閒置連線。
為了方便查詢,我們事先定義好一個結構體來儲存user表的資料。
type Info struct { Id int `json:"id"` Code string `json:"code"` HwCode string `json:"hw_code"` Name string `json:"name"` Des string `json:"des"` Created int64 `json:"created"` Updated int64 `json:"updated"` BrandId int64 `json:"brand_id"` }
單行查詢db.QueryRow()
執行一次查詢,並期望返回最多一行結果(即Row)。QueryRow總是返回非nil的值,直到返回值的Scan方法被呼叫時,才會返回被延遲的錯誤。(如:未找到結果)
func (db *DB) QueryRow(query string, args ...interface{}) *Row
具體範例程式碼:
func (o object) QueryInfo(id int) *Info { r := new(Info) var qstr string switch { case id > 0: qstr += fmt.Sprintf(" and id = %d", id) default: o.Log.Error("invalid param") return nil } sqlstr := ` select coalesce(id,0) as id, coalesce(code,'') as code, coalesce(hw_code,'') as hw_code, coalesce(name,'') as name, coalesce(des,'') as des, coalesce(created,0) as created, coalesce(updated,0) as updated, coalesce(brand_id,0) as brand_id from test_table where 1=1 ` sqlstr += qstr err := o.DbRo.QueryRow(sqlstr).Scan(&r.Id, &r.Code, &r.HwCode, &r.Name, &r.Des, &r.Created, &r.Updated, &r.BrandId) if err != nil { fmt.Println(err) o.Log.Errorf("param=%d,sql=%s,err=%v", id, sqlstr, err) return nil } return r }
多行查詢db.Query()
執行一次查詢,返回多行結果(即Rows),一般用於執行select命令。引數args表示query中的佔位引數。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
具體範例程式碼:
func (o object) QueryList(brandId int) (list []Info) { list = make([]Info, 0) var qstr string switch { case brandId > 0: qstr += fmt.Sprintf(" and brand_id = %d", brandId) default: o.Log.Error("invalid param") return nil } sqlstr := ` select coalesce(id,0) as id, coalesce(code,'') as code, coalesce(hw_code,'') as hw_code, coalesce(name,'') as name, coalesce(des,'') as des, coalesce(created,0) as created, coalesce(updated,0) as updated, coalesce(brand_id,0) as brand_id from bike_color where 1=1 ` sqlstr += qstr rows, err := o.DbRo.Query(sqlstr) if err != nil { o.Log.Error(err) return } defer rows.Close() // rows.Next()獲取下一條結果 for rows.Next() { var r = new(Info) err = rows.Scan(&r.Id, &r.Code, &r.HwCode, &r.Name, &r.Des, &r.Created, &r.Updated, &r.BrandId) if err != nil { o.Log.Error(err, brandId) continue } list = append(list, *r) } return }
插入、更新和刪除操作都使用Exec
方法。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
Exec執行一次命令(包括查詢、刪除、更新、插入等),返回的Result是對已執行的SQL命令的總結。引數args表示query中的佔位引數。
sqlStr := "update user set age=? where id = ?" ret, _:= db.Exec(sqlStr, 39, 3) n, _ := ret.RowsAffected() // 操作影響的行數
具體插入資料範例程式碼如下:
func (o object) Insert(i *Info) (r int) { sqlstr := `insert into bike_model(name,ctrl_tmpl,batt_support,bike_class,pics,code,brand_id,hw_code) values('%v',%d,%d,%d,'%v','%v',%d,'%v') returning id` sqlstr = fmt.Sprintf(sqlstr, i.Name, i.CtrlTmpl, i.BattSupport, i.BikeClass, i.Pics, i.Code, i.BrandId, i.HwCode) fmt.Println(sqlstr) err := o.Db.QueryRow(sqlstr).Scan(&r) if err != nil { fmt.Println(err) o.Log.Errorf("param=%+v,sql=%s,err=%v", i, sqlstr, err) return } return }
具體更新資料範例程式碼如下:
func (o object) Update(i *Info) (r int) { sqlstr := fmt.Sprintf("update bike_model set ") if i.Id <= 0 { o.Log.Error("invalid param") return } if len(i.Name) > 0 { sqlstr += fmt.Sprintf("name = '%v',", i.Name) } if i.CtrlTmpl > 0 { sqlstr += fmt.Sprintf("ctrl_tmpl = '%d',", i.CtrlTmpl) } if i.BattSupport > 0 { sqlstr += fmt.Sprintf("batt_support = '%d',", i.BattSupport) } if i.BikeClass > 0 { sqlstr += fmt.Sprintf("bike_class = '%d',", i.BikeClass) } if len(i.Pics) > 0 { sqlstr += fmt.Sprintf("hw_code = '%v',", i.Pics) } if len(i.Code) > 0 { sqlstr += fmt.Sprintf("code = '%v',", i.Code) } if i.BrandId > 0 { sqlstr += fmt.Sprintf("brand_id = %d,", i.BrandId) } if len(i.HwCode) > 0 { sqlstr += fmt.Sprintf("hw_code = '%v',", i.HwCode) } // 拼接sql字串 sqlstr = strings.TrimRight(sqlstr, ",") sqlstr += fmt.Sprintf("where id = %d returning id", i.Id) err := o.Db.QueryRow(sqlstr).Scan(&r) if err != nil { o.Log.Errorf("param=%+v,sql=%s,err=%v", i, sqlstr, err) return } return }
具體刪除資料的範例程式碼如下:
func (o object) Delete(id int) (r int) { sqlstr := `delete from bike_model where id = $1` res, err := o.Db.Exec(sqlstr, id) if err != nil { o.Log.Errorf("param=%d,sql=%s,err=%v", id, sqlstr, err) return } // 影響的行數 ra, _ := res.RowsAffected() if ra != 1 { o.Log.Warnf("param=%d,sql=%s,rowsAftected=%d", id, sqlstr, ra) return int(ra) } return }
普通SQL語句執行過程:
預處理執行過程:
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:%vn", err) return } defer stmt.Close() rows, err := stmt.Query(0) if err != nil { fmt.Printf("query failed, err:%vn", 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:%vn", err) return } fmt.Printf("id:%d name:%s age:%dn", 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:%vn", err) return } defer stmt.Close() _, err = stmt.Exec("小王子", 18) if err != nil { fmt.Printf("insert failed, err:%vn", err) return } _, err = stmt.Exec("沙河娜扎", 18) if err != nil { fmt.Printf("insert failed, err:%vn", err) return } fmt.Println("insert success.") }
我們任何時候都不應該自己拼接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:%sn", sqlStr) var u user err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("exec failed, err:%vn", err) return } fmt.Printf("user:%#vn", u) }
此時以下輸入字串都可以引發SQL隱碼攻擊問題:
sqlInjectDemo("xxx' or 1=1#") sqlInjectDemo("xxx' union select * from user #") sqlInjectDemo("xxx' and (select count(*) from user) <10 #")
補充:不同的資料庫中,SQL語句使用的預留位置語法不盡相同。
資料庫 | 預留位置語法 |
---|---|
MySQL | ? |
PostgreSQL | $1, $2等 |
SQLite | ? 和$1 |
Oracle | :name |
事務:一個最小的不可再分的工作單元;通常一個事務對應一個完整的業務(例如銀行賬戶轉賬業務,該業務就是一個最小的工作單元),同時這個完整的業務需要執行多次的DML(insert、update、delete)語句共同聯合完成。A轉賬給B,這裡面就需要執行兩次update操作。
在MySQL中只有使用了Innodb
資料庫引擎的資料庫或表才支援事務。事務處理可以用來維護資料庫的完整性,保證成批的SQL語句要麼全部執行,要麼全部不執行。
通常事務必須滿足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:%vn", 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:%vn", err) return } affRow1, err := ret1.RowsAffected() if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec ret1.RowsAffected() failed, err:%vn", 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:%vn", err) return } affRow2, err := ret2.RowsAffected() if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec ret1.RowsAffected() failed, err:%vn", 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!") }
以上就是golong操作連線資料庫實現mysql事務範例的詳細內容,更多關於golong操作實現mysql事務的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45