1. 介绍
Go
官网提供了 database
包,database
包下有 sql/driver
。该包用来定义操作数据库的接口,这保障了无论应用哪种数据库,操作形式都是雷同的。但Go
官网并没有提供连贯数据库的 driver
,如果要操作数据库,还须要第三方的driver
包。
2. 下载安装驱动
go-sql-driver
驱动源码地址: https://github.com/go-sql-dri…
2.1 装置驱动
go get -u github.com/go-sql-driver/mysql
3. 匿名导入
<font color=blue> 通常来说,导入包后就能调用该包中的数据和办法。然而对于数据库操作来说,不应该间接应用导入驱动包所提供的办法,而应该应用 sql.DB
对象所提供的对立的办法。因而在导入 MySQL
驱动时,应用了匿名导入包的形式。</font>
匿名导入包: 只导入包然而不应用包内的类型和数据,应用匿名的形式: 在包门路前增加下画线
_
import (_ "github.com/go-sql-driver/mysql")
在导入一个数据库驱动后,该驱动会自行初始化并注册到 Go
的database/sql 上
下文中,这样就能够通过 database/sql
包所提供的办法来拜访数据库了。
4. 连贯数据库
4.1 连贯办法
应用 sql
包中的 Open()
函数来连贯数据库。
Open(driverName, dataSourceName string) (*DB, error)
driverName
: 应用的驱动名, 如mysql
。(注册到database/sql
时所应用的名字)dataSourceName
: 数据库连贯信息, 格局:[用户名: 明码 @tcp(IP:port)/ 数据库?charset=utf8
], 例如:root:123456@tcp(127.0.0.1:3306)/test?charset=utf8
4.2 sql.DB 作用
sql.Open()
返回的sql.DB
对象是Goroutine
并发平安的。sql.DB
通过数据库驱动为开发者提供治理底层数据库连贯的关上和敞开操作。sql.DB
帮忙开发者治理数据库连接池。正在应用的连贯被标记为忙碌,用完后回到连接池期待下次应用。所以,<font color=red> 如果开发者没有把连贯开释回连接池,会导致过多连贯使系统资源耗尽。</font>
4.3 sql.DB 设计指标
sql.DB
的设计指标就是作为长连贯(一次连贯屡次数据交互)应用,不宜频繁开关。比拟好的做法是,为每个不同的 datastore
建一个 DB
对象,放弃这些对象关上。如果须要短连贯(一次连贯一次数据交互),就把 DB
作为参数传入 function
,而不要在function
中开关。
5. 写操作(增、删、改)
5.1 执行步骤
- 先应用
预编译语句(PreparedStatement)
来拼接sql
。 - 而后调用
db.Exec()
执行SQL
,返回执行后果
5.2 代码示例
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"time"
)
func main() {
// 连贯数据库
open, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/test?charset=utf8")
checkError(err)
// 插入数据
//add(open)
// 更新数据
//update(open)
// 删除数据
del(open)
}
// 插入数据
func add(open *sql.DB) {
// 插入数据
prepare, err := open.Prepare("insert user set username=?,password=?,mobile=?,createtime=?")
checkError(err)
exec, err := prepare.Exec("李四", "123456", "17600000000", time.Now().Unix())
checkError(err)
id, err := exec.LastInsertId()
checkError(err)
fmt.Printf("插入数据 ID: %d \n",id)
}
// 更新
func update(open *sql.DB) {prepare, err := open.Prepare("update user set username=? where id =?")
checkError(err)
exec, err := prepare.Exec("王五", "18")
checkError(err)
rows, err := exec.RowsAffected()
checkError(err)
fmt.Printf("更新数据胜利, 影响条数 %d \n",rows)
}
// 删除数据
func del(open *sql.DB) {prepare, err := open.Prepare("delete from user where id =?")
checkError(err)
exec, err := prepare.Exec("8")
checkError(err)
rows, err := exec.RowsAffected()
checkError(err)
fmt.Printf("删除数据胜利, 影响条数 %d \n",rows)
}
// 检测谬误
func checkError(err error) {
if err != nil {panic("操作失败:"+err.Error())
}
}
6. 读操作(查问)
6.1 执行步骤
1. 查问多条步骤
- 调用
db.Query()
办法执行SQL
语句,返回一个Rows
查问后果。 - 将
rows.Next()
办法的返回值作为for
循环的条件,迭代查问数据。 - 在循环中,通过
rows.Scan()
办法读取每一行数据。 - 调用
db.Close()
敞开查问。
2. 查问单条步骤
- 调用
db.QueryRow()
办法执行SQL
语句,返回一个Row
查问后果。 - 而后调用
row.Scan()
读取数据。
6.2 代码示例
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 连贯数据库
db, err := sql.Open("mysql", "root:root@tcp(127.0.0.1:3306)/nsbd_app?charset=utf8")
checkError(err)
// 查问多条数据
rows := queryRows(db)
fmt.Printf("多条返回: \n%+v\n",rows)
// 查问单条数据
row := queryRow(db)
fmt.Printf("单条返回: \n%+v\n",row)
}
// 创立表的映射对象
type User struct {
Uid int
UserName string
CreateTime int
Birthday sql.NullString // 有的值可能为 NULL
}
// 查问多条数据
func queryRows(db *sql.DB) []User {stmt, err := db.Prepare("select id,username,createtime,birthday from nsbd_user where id < ?")
checkError(err)
rows, err := stmt.Query(30)
// 提早敞开
defer rows.Close()
checkError(err)
user := new(User)
//users := make([]User,5)
var users []User
for rows.Next() {// rows.Scan()办法的参数程序很重要,必须和查问后果的 column 绝对应(数量和程序都须要统一)err := rows.Scan(&user.Uid, &user.UserName, &user.CreateTime, &user.Birthday)
checkError(err)
users = append(users, *user)
}
return users
}
// 查问单条数据
func queryRow(db *sql.DB) User {stmt, err := db.Prepare("select id,username,createtime,birthday from nsbd_user where id = ?")
checkError(err)
user := new(User)
err = stmt.QueryRow(4).Scan(&user.Uid, &user.UserName, &user.CreateTime, &user.Birthday)
checkError(err)
return *user
}
// 检测谬误
func checkError(err error) {
if err != nil {panic("操作失败:" + err.Error())
}
}
输入:
多条返回:
[{Uid:1 UserName:admin CreateTime:0 Birthday:{String:2017-04-15 Valid:true}} {Uid:2 UserName:u2 CreateTime:1605858072 Birthday:{String:1993-02-14 Valid:true}} {Uid:3 UserName:u3 CreateTime:1606289644 Birthday:{String:1991-05-31 Valid:true}} {Uid:4 UserName:u4 CreateTime:1610521164 Birthday:{String:1989-11-24 Valid:true}} {Uid:5 UserName:u5 CreateTime:1610588359 Birthday:{String: Valid:false}}]
单条返回:
{Uid:4 UserName:u4 CreateTime:1610521164 Birthday:{String:1989-11-24 Valid:true}}
6.3 注意事项
rows.Scan()
办法的参数程序很重要,必须和查问后果的column
绝对应(数量和程序都须要统一);Go
是强类型语言,在查问数据时先定义数据类型,针对字段值为NULL
时,数据类型应定义为:sql.NullString、sql.NullInt64
等, 并能够通过Valid
值来判断查问到的值是赋值状态还是未赋值状态。- 每次
db.Query()
操作后,都倡议调用rows.Close()
。rows.Close()
操作是幂等操作,即使对已敞开的rows
再执行close()
也没关系。
6.4 为什么查问后要敞开连贯?
因为 db.Query()
会从数据库连接池中获取一个连贯,这个底层连贯在后果集(rows
)未敞开前会被标记为处于忙碌状态。当遍历读到最初一条记录时,会产生一个外部 EOF
谬误,主动调用 rows.Close()
。但如果出现异常,提前退出循环,rows
不会敞开,连贯不会回到连接池中,连贯也不会敞开,则此连贯会始终被占用。因而通常应用 defer rows.Close()
来确保数据库连贯能够正确放回到连接池中。
本文由 mdnice 多平台公布