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)
}
相关文章
相关标签/搜索