Go語言沒有提供官方的數據庫驅動,所以要安裝第三方函數庫。
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main() {
//數據庫連接 db,_:=sql.Open("mysql","root:root@(127.0.0.1:3306)/golang") err :=db.Ping() if err != nil{ fmt.Println("數據庫鏈接失敗") }
defer db.Close()
//多行查詢
rows,_:=db.Query("select * from stu")
var id,name string
for rows.Next(){
rows.Scan(&id,&name)
fmt.Println(id,name)
}
}
CRUD
建庫建表
我們先在MySQL中創建一個名為sql_test
的數據庫
CREATE DATABASE sql_test;
進入該數據庫:
use sql_test;
執行以下命令創建一張用於測試的數據表:
CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) DEFAULT '', `age` INT(11) DEFAULT '0', PRIMARY KEY(`id`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
查詢
單行查詢
單行查詢db.QueryRow()
執行一次查詢,並期望返回最多一行結果(即Row)。QueryRow總是返回非nil的值,直到返回值的Scan方法被調用時,才會返回被延遲的錯誤。(如:未找到結果)
func (db *DB) QueryRow(query string, args ...interface{}) *Row
// 查詢單條數據示例 func queryRowDemo() { sqlStr := "select id, name, age from user where id=?" var u user // 非常重要:確保QueryRow之后調用Scan方法,否則持有的數據庫鏈接不會被釋放 err := db.QueryRow(sqlStr, 1).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) }
多行查詢
多行查詢db.Query()
執行一次查詢,返回多行結果(即Rows),一般用於執行select命令。參數args表示query中的占位參數。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
具體示例代碼:
// 查詢多條數據示例 func queryMultiRowDemo() { sqlStr := "select id, name, age from user where id > ?" rows, err := db.Query(sqlStr, 0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } // 非常重要:關閉rows釋放持有的數據庫鏈接 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 (db *DB) Exec(query string, args ...interface{}) (Result, error)
Exec執行一次命令(包括查詢、刪除、更新、插入等),返回的Result是對已執行的SQL命令的總結。參數args表示query中的占位參數。
具體插入數據示例代碼如下:
// 插入數據 func insertRowDemo() { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "王五", 38) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } theID, err := ret.LastInsertId() // 新插入數據的id if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n", err) return } fmt.Printf("insert success, the id is %d.\n", theID) }
更新數據
具體更新數據示例代碼如下:
// 更新數據 func updateRowDemo() { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39, 3) if err != nil { fmt.Printf("update failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) }
刪除數據
具體刪除數據的示例代碼如下:
// 刪除數據 func deleteRowDemo() { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 3) if err != nil { fmt.Printf("delete failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) }
MySQL預處理
什么是預處理?
普通SQL語句執行過程:
- 客戶端對SQL語句進行占位符替換得到完整的SQL語句。
- 客戶端發送完整SQL語句到MySQL服務端
- MySQL服務端執行完整的SQL語句並將結果返回給客戶端。
預處理執行過程:
- 把SQL語句分成兩部分,命令部分與數據部分。
- 先把命令部分發送給MySQL服務端,MySQL服務端進行SQL預處理。
- 然后把數據部分發送給MySQL服務端,MySQL服務端對SQL語句進行占位符替換。
- MySQL服務端執行完整的SQL語句並將結果返回給客戶端。
為什么要預處理?
- 優化MySQL服務器重復執行SQL的方法,可以提升服務器性能,提前讓服務器編譯,一次編譯多次執行,節省后續編譯的成本。
- 避免SQL注入問題。
Go實現MySQL預處理
Go中的
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.") }
Go實現MySQL事務
事務相關方法
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:%v\n", err) return } sqlStr1 := "Update user set age=30 where id=?" _, err = tx.Exec(sqlStr1, 2) if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec sql1 failed, err:%v\n", err) return } sqlStr2 := "Update user set age=40 where id=?" _, err = tx.Exec(sqlStr2, 4) if err != nil { tx.Rollback() // 回滾 fmt.Printf("exec sql2 failed, err:%v\n", err) return } err = tx.Commit() // 提交事務 if err != nil { tx.Rollback() // 回滾 fmt.Printf("commit failed, err:%v\n", err) return } fmt.Println("exec trans success!") }
sqlx使用
第三方庫sqlx
能夠簡化操作,提高開發效率。
安裝
go get github.com/jmoiron/sqlx
基本使用
連接數據庫
var db *sqlx.DB func initDB() (err error) { dsn := "user:password@tcp(127.0.0.1:3306)/test" // 也可以使用MustConnect連接不成功就panic db, err = sqlx.Connect("mysql", dsn) if err != nil { fmt.Printf("connect DB failed, err:%v\n", err) return } db.SetMaxOpenConns(20) db.SetMaxIdleConns(10) return }
查詢
查詢單行數據示例代碼如下:
// 查詢單條數據示例 func queryRowDemo() { sqlStr := "select id, name, age from user where id=?" var u user err := db.Get(&u, sqlStr, 1) if err != nil { fmt.Printf("get failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.ID, u.Name, u.Age) }
查詢多行數據示例代碼如下:
// 查詢多條數據示例 func queryMultiRowDemo() { sqlStr := "select id, name, age from user where id > ?" var users []user err := db.Select(&users, sqlStr, 0) if err != nil { fmt.Printf("query failed, err:%v\n", err) return } fmt.Printf("users:%#v\n", users) }
插入、更新和刪除
sqlx中的exec方法與原生sql中的exec使用基本一致:
// 插入數據 func insertRowDemo() { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "沙河小王子", 19) if err != nil { fmt.Printf("insert failed, err:%v\n", err) return } theID, err := ret.LastInsertId() // 新插入數據的id if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n", err) return } fmt.Printf("insert success, the id is %d.\n", theID) }
// 更新數據 func updateRowDemo() { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39, 6) if err != nil { fmt.Printf("update failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) }
// 刪除數據 func deleteRowDemo() { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 6) if err != nil { fmt.Printf("delete failed, err:%v\n", err) return } n, err := ret.RowsAffected() // 操作影響的行數 if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) }
事務操作
對於事務操作,我們可以使用sqlx
中提供的db.Beginx()
和tx.MustExec()
方法來簡化錯誤處理過程。示例代碼如下:
func transactionDemo() { tx, err := db.Beginx() // 開啟事務 if err != nil { if tx != nil { tx.Rollback() } fmt.Printf("begin trans failed, err:%v\n", err) return } sqlStr1 := "Update user set age=40 where id=?" tx.MustExec(sqlStr1, 2) sqlStr2 := "Update user set age=50 where id=?" tx.MustExec(sqlStr2, 4) err = tx.Commit() // 提交事務 if err != nil { tx.Rollback() // 回滾 fmt.Printf("commit failed, err:%v\n", err) return } fmt.Println("exec trans success!") }
注意事項
SQL中的占位符
不同的數據庫中,SQL語句使用的占位符語法不盡相同。
數據庫 | 占位符語法 |
---|---|
MySQL | ? |
PostgreSQL | $1 , $2 等 |
SQLite | ? 和$1 |
Oracle | :name |
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 users []user err := db.Select(&users, sqlStr) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } for _, u := range users { 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 #")