Go語言中的database/sql
包提供了保證SQL或類SQL數據庫的泛用接口,並不提供具體的數據庫驅動。使用database/sql
包時必須注入(至少)一個數據庫驅動。mysql
咱們經常使用的數據庫基本上都有完整的第三方實現。例如:MySQL驅動git
go get -u github.com/go-sql-driver/mysql
func Open(driverName, dataSourceName string) (*DB, error)
Open打開一個dirverName指定的數據庫,dataSourceName指定數據源,通常包至少括數據庫文件名和(可能的)鏈接信息。github
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() }
Open函數可能只是驗證其參數,而不建立與數據庫的鏈接。若是要檢查數據源的名稱是否合法,應調用返回值的Ping方法。sql
返回的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)/test" // 不會校驗帳號密碼是否正確 // 注意!!!這裏不要使用:=,咱們是給全局變量賦值,而後在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:%v\n", err) return } }
其中sql.DB
是一個數據庫(操做)句柄,表明一個具備零到多個底層鏈接的鏈接池。它能夠安全的被多個go程同時使用。database/sql
包會自動建立和釋放鏈接;它也會維護一個閒置鏈接的鏈接池。安全
func (db *DB) SetMaxOpenConns(n int)
SetMaxOpenConns
設置與數據庫創建鏈接的最大數目。 若是n大於0且小於最大閒置鏈接數,會將最大閒置鏈接數減少到匹配最大開啓鏈接數的限制。 若是n<=0,不會限制最大開啓鏈接數,默認爲0(無限制)。bash
func (db *DB) SetMaxIdleConns(n int)
SetMaxIdleConns設置鏈接池中的最大閒置鏈接數。 若是n大於最大開啓鏈接數,則新的最大閒置鏈接數會減少到匹配最大開啓鏈接數的限制。 若是n<=0,不會保留閒置鏈接。服務器
咱們先在MySQL中建立一個名爲sql_test
的數據庫併發
CREATE DATABASE sql_test;
進入該數據庫:tcp
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) }
普通SQL語句執行過程:
預處理執行過程:
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.") }
事務:一個最小的不可再分的工做單元;一般一個事務對應一個完整的業務(例如銀行帳戶轉帳業務,該業務就是一個最小的工做單元),同時這個完整的業務須要執行屢次的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:%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
可以簡化操做,提升開發效率。
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語句使用的佔位符語法不盡相同。
數據庫 | 佔位符語法 |
---|---|
MySQL | ? |
PostgreSQL | $1 , $2 等 |
SQLite | ? 和$1 |
Oracle | :name |
咱們任什麼時候候都不該該本身拼接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 #")