Golang SQL 操做初體驗

簡介

Golang 提供了 database/sql 包用於對 SQL 的數據庫的訪問, 在這個包中, 最重要的天然就是 sql.DB 了.
對於 sql.DB, 咱們須要強調的是, 它並不表明一個數據庫鏈接, 它是一個已存在的數據庫的抽象訪問接口. sql.DB 爲咱們提供了兩個重要的功能:mysql

  • sql.DB 經過數據庫驅動爲咱們管理底層數據庫鏈接的打開和關閉操做.git

  • sql.DB 爲咱們管理數據庫鏈接池github

有一點須要注意的是, 正由於 sql.DB 是以鏈接池的方式管理數據庫鏈接, 咱們每次進行數據庫操做時, 都須要從鏈接池中取出一個鏈接, 當操做任務完成時, 咱們須要將此鏈接返回到鏈接池中, 所以若是咱們沒有正確地將鏈接返回給鏈接池, 那麼會形成 db.SQL 打開過多的數據庫鏈接, 使數據庫鏈接資源耗盡.golang

MySQL 數據庫的基本操做

數據庫驅動的導入

有過數據庫開發經驗的朋友就知道了, 咱們須要藉助於一個數據庫驅動才能和具體的數據庫進行鏈接. 這在 Golang 中也不例外. 例如以 MySQL 數據庫爲例:sql

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

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

數據庫的鏈接

當導入了 MySQL 驅動後, 咱們打開數據庫鏈接:網絡

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

經過 sql.Open 函數, 能夠建立一個數據庫抽象操做接口, 若是打開成功的話, 它會返回一個 sql.DB 指針.
sql.Open 函數的簽名以下:tcp

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

它接收兩個參數:函數

  • driverName, 使用的驅動名. 這個名字其實就是數據庫驅動註冊到 database/sql 時所使用的名字.指針

  • dataSourceName, 第二個數據庫鏈接的連接. 這個連接包含了數據庫的用戶名, 密碼, 數據庫主機以及須要鏈接的數據庫名等信息.

須要注意的是, golang 對數據庫的鏈接是延時初始化的(lazy init), 即 sql.Open 並不會當即創建一個數據庫的網絡鏈接, 也不會對數據庫連接參數的合法性作檢驗, 它僅僅是初始化一個 sql.DB 對象. 當咱們進行第一次數據庫查詢操做時, 此時纔會真正創建網絡鏈接.
若是咱們想當即檢查數據庫鏈接是否可用, 那麼能夠利用 sql.DB 的 Ping 方法, 例如:

err = db.Ping()
if err != nil {
    log.Fatal(err)
}

sql.DB 的最佳實踐:
sql.DB 對象是做爲長期生存的對象來使用的, 咱們應當避免頻繁地調用 Open() 和 Close(). 即通常來講, 咱們要對一個數據庫進行操做時, 建立一個 sql.DB 並將其保存起來, 每次操做此數據庫時, 傳遞此 sql.DB 對象便可, 最後在須要對此數據庫進行訪問時, 關閉對應的 sql.DB 對象.

數據庫的查詢

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

  • 調用 db.Query 執行 SQL 語句, 此方法會返回一個 Rows 做爲查詢的結果

  • 經過 rows.Next() 迭代查詢數據.

  • 經過 rows.Scan() 讀取每一行的值

  • 調用 db.Close() 關閉查詢

例如咱們有以下一個數據庫表:

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

咱們向其中插入一條記錄:

func insertData(db *sql.DB) {
    rows, err := db.Query(`INSERT INTO user (id, name, age) VALUES (1, "xys", 20)`)
    defer rows.Close()
    if err != nil {
        log.Fatalf("insert data error: %v\n", err)
    }

    var result int
    rows.Scan(&result)
    log.Printf("insert result %v\n", result)
}

經過調用 db.Query, 咱們執行了一條 INSERT 語句插入了一條數據. 當執行完畢後, 首先須要作的是檢查語句是否執行成功, 當沒有錯誤時, 就經過 rows.Scan 獲取執行的結果. 由於 INSERT 返回的是插入的數據的行數, 所以咱們打印的語句就是 "insert result 0".

接下來如法炮製, 咱們從數據庫中將插入的數據取出:

func selectData(db *sql.DB) {
    var id int
    var name string
    var age int
    rows, err := db.Query(`SELECT * From user where id = 1`)
    if err != nil {
        log.Fatalf("insert data error: %v\n", err)
        return
    }
    for rows.Next() {
        rows.Scan(&id, &age, &name)
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("get data, id: %d, name: %s, age: %d", id, name, age)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

上面的代碼的流程基本上沒有很大的差異, 不過咱們須要注意一點的是 rows.Scan 參數的順序很重要, 須要和查詢的結果的 column 對應. 例如 "SELECT * From user where id = 1" 查詢的行的 column 順序是 "id, name, age", 所以 rows.Scan 也須要按照此順序 rows.Scan(&id, &name, &age), 否則會形成數據讀取的錯位.

注意:

  1. 對於每一個數據庫操做都須要檢查是否有錯誤返回

  2. 每次 db.Query 操做後, 都須要調用 rows.Close(). 由於 db.Query() 會從數據庫鏈接池中獲取一個鏈接, 若是咱們沒有調用 rows.Close(), 則此鏈接會一直被佔用. 所以一般咱們使用 defer rows.Close() 來確保數據庫鏈接能夠正確放回到鏈接池中.

  3. 屢次調用 rows.Close() 不會有反作用, 所以即便咱們已經顯示地調用了 rows.Close(), 咱們仍是應該使用 defer rows.Close() 來關閉查詢.

完整的例子以下:

func insertData(db *sql.DB) {
    rows, err := db.Query(`INSERT INTO user (id, name, age) VALUES (1, "xys", 20)`)
    defer rows.Close()

    if err != nil {
        log.Fatalf("insert data error: %v\n", err)
    }

    var result int
    rows.Scan(&result)
    log.Printf("insert result %v\n", result)
}

func selectData(db *sql.DB) {
    var id int
    var name string
    var age int
    rows, err := db.Query(`SELECT id, name, age From user where id = 1`)
    if err != nil {
        log.Fatalf("insert data error: %v\n", err)
        return
    }
    for rows.Next() {
        err = rows.Scan(&id, &name, &age)
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("get data, id: %d, name: %s, age: %d", id, name, age)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")

    defer db.Close()

    if err != nil {
        fmt.Printf("connect to db 127.0.0.1:3306 error: %v\n", err)
        return
    }

    insertData(db)

    selectData(db)
}

預編譯語句(Prepared Statement)

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

  • PreparedStatement 能夠實現自定義參數的查詢

  • PreparedStatement 一般來講, 比手動拼接字符串 SQL 語句高效.

  • PreparedStatement 能夠防止SQL注入攻擊

下面咱們將上一小節的例子使用 Prepared Statement 來改寫:

func deleteData(db *sql.DB) {
    stmt, _ := db.Prepare(`DELETE FROM user WHERE id = ?`)

    rows, err := stmt.Query(1)
    defer stmt.Close()

    rows.Close()
    if err != nil {
        log.Fatalf("delete data error: %v\n", err)
    }

    rows, err = stmt.Query(2)
    rows.Close()
    if err != nil {
        log.Fatalf("delete data error: %v\n", err)
    }
}

func insertData(db *sql.DB) {
    stmt, _ := db.Prepare(`INSERT INTO user (id, name, age) VALUES (?, ?, ?)`)

    rows, err := stmt.Query(1, "xys", 20)
    defer stmt.Close()

    rows.Close()
    if err != nil {
        log.Fatalf("insert data error: %v\n", err)
    }

    rows, err = stmt.Query(2, "test", 19)
    var result int
    rows.Scan(&result)
    log.Printf("insert result %v\n", result)
    rows.Close()
}

func selectData(db *sql.DB) {
    var id int
    var name string
    var age int
    stmt, _ := db.Prepare(`SELECT * From user where age > ?`)

    rows, err := stmt.Query(10)

    defer stmt.Close()
    defer rows.Close()

    if err != nil {
        log.Fatalf("select data error: %v\n", err)
        return
    }
    for rows.Next() {
        err = rows.Scan(&id, &name, &age)
        if err != nil {
            log.Fatal(err)
        }
        log.Printf("get data, id: %d, name: %s, age: %d", id, name, age)
    }

    err = rows.Err()
    if err != nil {
        log.Fatal(err)
    }
}

func main() {
    db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test")

    defer db.Close()

    if err != nil {
        fmt.Printf("connect to db 127.0.0.1:3306 error: %v\n", err)
        return
    }

    deleteData(db)

    insertData(db)

    selectData(db)
}
相關文章
相關標籤/搜索