GoLang DB 操作简介

2018-05-19 golang database

GoLang 提供了标准包用于对 SQL 数据库进行访问,作为操作数据库的入口对象 sql.DB, 主要为提供了两个重要的功能:A) 提供管理底层数据库连接的打开和关闭操作;B) 管理数据库连接池。

需要注意的是,sql.DB 表示操作数据库的抽象访问接口,而非一个数据库连接对象,会根据实际的驱动打开关闭数据库连接,管理连接池。

这里简单介绍 MySQL 的使用方式。

简介

如下的示例中都是使用 test.users 表。

CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;

DROP TABLE IF EXISTS `users`;
CREATE TABLE IF NOT EXISTS `users` (
    `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `name` CHAR(64) NOT NULL COMMENT "用户名",
    `age` INT NOT NULL COMMENT "用户的年龄",
    `gender` ENUM('no', 'male', 'female') DEFAULT 'no' COMMENT "性别",
    `gmt_modify` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `gmt_create` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COMMENT "用户列表";

INSERT INTO users(name, age, gender) VALUES("Atelier", 29, "male");
INSERT INTO users(name, age, gender) VALUES("Kingsley", 39, "male");
INSERT INTO users(name, age, gender) VALUES("Gwyneth", 19, "female");

安装驱动

也就是安装 MySQL 的驱动。

$ go get github.com/go-sql-driver/mysql

建立连接

在访问数据库前,需要先建立链接,也就是用到 database/sql 中的 Open() 函数,示例如下。

db, err := sql.Open("mysql", "root:yourpassword@tcp(127.0.0.1:3306)/yourdatabase")

上述的第二个参数表示连接 DB 的方式,也就是使用 root 用户,密码是 yourpassword,使用 TCP 协议,数据库 IP 地址为 127.0.0.1:3306,当前使用的数据库是 yourdatabase

MySQL 的连接方式有很多种,除了上述方式外,也可以参考如下。

user@unix(/path/to/socket)/dbname?charset=utf8
user:password@tcp(localhost:5555)/dbname?charset=utf8
user:password@/dbname
user:password@tcp([de:ad:be:ef::ca:fe]:80)/dbname

查询

当建立了数据库的连接之后,就可以执行 SQL 查询语句了。

rows, err := db.Query("SELECT * FROM users")

然后用 for 循环遍历返回的结果,如果已知类型,那么可以直接转换,也可以使用通用的。

修改

可以使用 Prepare() 语句,然后在执行时添加参数,如果未使用占位符,在执行 Exec() 时参数可以为空。

stmt, err := db.Prepare("INSERT INTO users(name, age, gender) VALUES(?, ?, ?);")
res, err := stmt.Exec("Andy", 14, "male")

连接池

sql.Open() 实际上是返回一个连接池对象,而不是单个连接,在打开时并没有去连接数据库,只有在执行 Query()Exce() 时才会去实际连接数据库。

这就意味着在一个应用中,同样的库连接只需保存一个 sql.Open() 返回 DB 对象即可,而不需要多次 Open()

var db *sql.DB
func init() {
    db, _ = sql.Open("mysql", "root:@tcp(127.0.0.1:3306)/test?charset=utf8")
    db.SetMaxOpenConns(2000)
    db.SetMaxIdleConns(1000)
    db.Ping()
}

连接池的实现关键在于 SetMaxOpenConns()SetMaxIdleConns() ,其中,前者用于设置最大打开的连接数,默认值为 0 表示不限制;后者用于设置闲置的连接数。

示例

package main

import (
    "fmt"
    "log"

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

func DoQuery(db *sql.DB) {
    rows, err := db.Query("SELECT name, age, gender FROM users;")
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    cloumns, err := rows.Columns() // get columns' name
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(cloumns)
    fmt.Println("------------------")

    for rows.Next() {
        var name, gender string
        var age int

        err := rows.Scan(&name, &age, &gender)
        if err != nil {
            log.Fatal(err)
        }
        fmt.Println(name, age, gender)
    }

    /*
    values := make([]sql.RawBytes, len(cloumns))
    scanArgs := make([]interface{}, len(values))
    for i := range values {
        scanArgs[i] = &values[i]
    }

    for rows.Next() {
        err = rows.Scan(scanArgs...)
        if err != nil {
            log.Fatal(err)
        }

        var value string
        for i, col := range values {
            if col == nil {
                value = "NULL"
            } else {
                value = string(col)
            }
            fmt.Println(cloumns[i], ": ", value)
        }
        fmt.Println("------------------")
    }
    */

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

func DoInsert(db *sql.DB) {
    stmt, err := db.Prepare("INSERT INTO users(name, age, gender) VALUES(?, ?, ?);")
    if err != nil {
        log.Fatal(err)
    }

    res, err := stmt.Exec("Andy", 14, "male")
    if err != nil {
        log.Fatal(err)
    }
    lastId, err := res.LastInsertId()
    if err != nil {
        log.Fatal(err)
    }
    rowCnt, err := res.RowsAffected()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("ID=%d, affected=%d\n", lastId, rowCnt)
}

func main() {
    db, err := sql.Open("mysql", "root:@tcp(localhost:5506)/test")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    DoQuery(db)
    DoInsert(db)
}

<!- 如果长时间没有使用可能会有如下的报错 [mysql] 2023/09/27 06:34:23 packets.go:123: closing bad idle connection: unexpected read from socket [mysql] 2023/09/27 06:34:23 connection.go:173: driver: bad connection –>