Golang SQL 操作初體驗

lagunicage 8年前發布 | 10K 次閱讀 SQL Go語言 Google Go/Golang開發

簡介

Golang 提供了 database/sql 包用于對 SQL 的數據庫的訪問, 在這個包中, 最重要的自然就是 sql.DB 了.

對于 sql.DB , 我們需要強調的是, 它并不代表一個數據庫連接 , 它是一個已存在的數據庫的抽象訪問接口. sql.DB 為我們提供了兩個重要的功能:

  • sql.DB 通過數據庫驅動為我們管理底層數據庫連接的打開和關閉操作.

  • sql.DB 為我們管理數據庫連接池

有一點需要注意的是, 正因為 sql.DB 是以連接池的方式管理數據庫連接, 我們每次進行數據庫操作時, 都需要從連接池中取出一個連接, 當操作任務完成時, 我們需要將此連接返回到連接池中, 因此如果我們沒有正確地將連接返回給連接池, 那么會造成 db.SQL 打開過多的數據庫連接, 使數據庫連接資源耗盡.

MySQL 數據庫的基本操作

數據庫驅動的導入

有過數據庫開發經驗的朋友就知道了, 我們需要借助于一個數據庫驅動才能和具體的數據庫進行連接. 這在 Golang 中也不例外. 例如以 MySQL 數據庫為例:

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 函數的簽名如下:

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)

}</code></pre>

通過調用 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)
}

}</code></pre>

上面的代碼的流程基本上沒有很大的差別, 不過我們需要注意一點的是 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)

}</code></pre>

預編譯語句(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)

}</code></pre>

 

來自:https://segmentfault.com/a/1190000006885571

 

 本文由用戶 lagunicage 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!