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 –>