golang操做mysql使用總結

 

sql.DB 經過數據庫驅動爲咱們提供管理底層數據庫鏈接的打開和關閉操做.
sql.DB 爲咱們管理數據庫鏈接池
須要注意的是,sql.DB表示操做數據庫的抽象訪問接口,而非一個數據庫鏈接對象;它能夠根據driver打開關閉數據庫鏈接,管理鏈接池。正在使用的鏈接被標記爲繁忙,用完後回到鏈接池等待下次使用。因此,若是你沒有把鏈接釋放回鏈接池,會致使過多鏈接使系統資源耗盡。

操做mysql

1.導入mysql數據庫驅動mysql

1
2
3
4
import (
   "database/sql"
   _ "github.com/go-sql-driver/mysql"
)

 

一般來講, 不該該直接使用驅動所提供的方法, 而是應該使用 sql.DB, 所以在導入 mysql 驅動時, 這裏使用了匿名導入的方式(在包路徑前添加 _), 當導入了一個數據庫驅動後, 此驅動會自行初始化並註冊本身到Golang的database/sql上下文中, 所以咱們就能夠經過 database/sql 包提供的方法訪問數據庫了.git

2.鏈接數據庫github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type DbWorker struct {
    //mysql data source name
    Dsn string 
}

func main() {
    dbw := DbWorker{
        Dsn: "user:password@tcp(127.0.0.1:3306)/test",
    }	
    db, err := sql.Open("mysql",
        dbw.Dsn)
    if err != nil {
        panic(err)
        return
    }
    defer db.Close()
}

 

經過調用sql.Open函數返回一個sql.DB指針; sql.Open函數原型以下:golang

1
func Open(driverName, dataSourceName string) (*DB, error)

 

  • driverName: 使用的驅動名. 這個名字其實就是數據庫驅動註冊到 database/sql 時所使用的名字.
  • dataSourceName: 數據庫鏈接信息,這個鏈接包含了數據庫的用戶名, 密碼, 數據庫主機以及須要鏈接的數據庫名等信息.
  1. sql.Open並不會當即創建一個數據庫的網絡鏈接, 也不會對數據庫連接參數的合法性作檢驗, 它僅僅是初始化一個sql.DB對象. 當真正進行第一次數據庫查詢操做時, 此時纔會真正創建網絡鏈接;
  2. sql.DB表示操做數據庫的抽象接口的對象,但不是所謂的數據庫鏈接對象,sql.DB對象只有當須要使用時纔會建立鏈接,若是想當即驗證鏈接,須要用Ping()方法;
  3. sql.Open返回的sql.DB對象是協程併發安全的.
  4. sql.DB的設計就是用來做爲長鏈接使用的。不要頻繁Open, Close。比較好的作法是,爲每一個不一樣的datastore建一個DB對象,保持這些對象Open。若是須要短鏈接,那麼把DB做爲參數傳入function,而不要在function中Open, Close。

3.數據庫基本操做sql

數據庫查詢的通常步驟以下:數據庫

  1. 調用 db.Query 執行 SQL 語句, 此方法會返回一個 Rows 做爲查詢的結果
  2. 經過 rows.Next() 迭代查詢數據.
  3. 經過 rows.Scan() 讀取每一行的值
  4. 調用 db.Close() 關閉查詢

現有user數據庫表以下:安全

1
2
3
4
5
6
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=3 DEFAULT CHARSET=utf8mb4

 

MySQL 5.5 以前, UTF8 編碼只支持1-3個字節,從MYSQL5.5開始,可支持4個字節UTF編碼utf8mb4,一個字符最多能有4字節,utf8mb4兼容utf8,因此能支持更多的字符集;關於emoji表情的話mysql的utf8是不支持,須要修改設置爲utf8mb4,才能支持。

查詢數據網絡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func (dbw *DbWorker) QueryData() {
	dbw.QueryDataPre()
	rows, err := dbw.Db.Query(`SELECT * From user where age >= 20 AND age < 30`)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

 

  1. rows.Scan 參數的順序很重要, 須要和查詢的結果的column對應. 例如 「SELECT * From user where age >=20 AND age < 30」 查詢的行的 column 順序是 「id, name, age」 和插入操做順序相同, 所以 rows.Scan 也須要按照此順序 rows.Scan(&id, &name, &age), 否則會形成數據讀取的錯位.
  2. 由於golang是強類型語言,因此查詢數據時先定義數據類型,可是查詢數據庫中的數據存在三種可能:存在值,存在零值,未賦值NULL 三種狀態, 由於能夠將待查詢的數據類型定義爲sql.Nullxxx類型,能夠經過判斷Valid值來判斷查詢到的值是否爲賦值狀態仍是未賦值NULL狀態.
  3. 每次db.Query操做後, 都建議調用rows.Close(). 由於 db.Query() 會從數據庫鏈接池中獲取一個鏈接, 這個底層鏈接在結果集(rows)未關閉前會被標記爲處於繁忙狀態。當遍歷讀到最後一條記錄時,會發生一個內部EOF錯誤,自動調用rows.Close(),但若是提早退出循環,rows不會關閉,鏈接不會回到鏈接池中,鏈接也不會關閉, 則此鏈接會一直被佔用. 所以一般咱們使用 defer rows.Close() 來確保數據庫鏈接能夠正確放回到鏈接池中; 不過閱讀源碼發現rows.Close()操做是冪等操做,即一個冪等操做的特色是其任意屢次執行所產生的影響均與一次執行的影響相同, 因此即使對已關閉的rows再執行close()也不要緊.

單行查詢併發

1
2
3
4
5
6
var name string
err = db.QueryRow("select name from user where id = ?", 1).Scan(&name)
if err != nil {
    log.Fatal(err)
}
fmt.Println(name)

 

  1. err在Scan後才產生,上述鏈式寫法是對的
  2. 須要注意Scan()中變量和順序要和前面Query語句中的順序一致,不然查出的數據會映射不一致.

插入數據tcp

1
2
3
4
5
6
7
8
9
10
11
12
13
func (dbw *DbWorker) insertData() {
	ret, err := dbw.Db.Exec(`INSERT INTO user (name, age) VALUES ("xys", 23)`)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

 

經過db.Exec()插入數據,經過返回的err可知插入失敗的緣由,經過返回的ret能夠進一步查詢本次插入數據影響的行數RowsAffected和最後插入的Id(若是數據庫支持查詢最後插入Id).

github完整代碼示例

4.預編譯語句(Prepared Statement)
預編譯語句(PreparedStatement)提供了諸多好處, 所以咱們在開發中儘可能使用它. 下面列出了使用預編譯語句所提供的功能:

  • PreparedStatement 能夠實現自定義參數的查詢
  • PreparedStatement 一般來講, 比手動拼接字符串 SQL 語句高效.
  • PreparedStatement 能夠防止SQL注入攻擊

通常用Prepared StatementsExec()完成INSERTUPDATEDELETE操做。

下面是將上述案例用Prepared Statement 修改以後的完整代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

type DbWorker struct {
	Dsn      string
	Db       *sql.DB
	UserInfo userTB
}
type userTB struct {
	Id   int
	Name sql.NullString
	Age  sql.NullInt64
}

func main() {
	var err error
	dbw := DbWorker{
		Dsn: "root:123456@tcp(localhost:3306)/sqlx_db?charset=utf8mb4",
	}
	dbw.Db, err = sql.Open("mysql", dbw.Dsn)
	if err != nil {
		panic(err)
		return
	}
	defer dbw.Db.Close()

	dbw.insertData()
	dbw.queryData()
}

func (dbw *DbWorker) insertData() {
	stmt, _ := dbw.Db.Prepare(`INSERT INTO user (name, age) VALUES (?, ?)`)
	defer stmt.Close()

	ret, err := stmt.Exec("xys", 23)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

func (dbw *DbWorker) QueryDataPre() {
	dbw.UserInfo = userTB{}
}
func (dbw *DbWorker) queryData() {
	stmt, _ := dbw.Db.Prepare(`SELECT * From user where age >= ? AND age < ?`)
	defer stmt.Close()

	dbw.QueryDataPre()

	rows, err := stmt.Query(20, 30)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

 

db.Prepare()返回的statement使用完以後須要手動關閉,即defer stmt.Close()
相關文章
相關標籤/搜索