Go使用MySQL数据库
连接数据库#
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "github.com/mikespook/mymysql"
)
func main() {
var db *sql.DB
// 第二个参数的格式为:user:password@tcp(ip: port)/dbname
db, _ = sql.Open("mysql", "******")
err := db.Ping()
if err != nil {
log.Fatalln(err.Error())
}
fmt.Println("CONNECTION SUCCESSFUL")
}
数据库驱动#
想要连接到SQL数据库,那么首先需要加载目标数据库的驱动,驱动中包含了与数据库交互的逻辑。正常获取数据库驱动的方法是调用sql.Register()函数进行注册:
var (
driversMu sync.RWMutex
// 使用一个map来储存用户定义的数据库驱动
drivers = make(map[string]driver.Driver)
)
func Register(name string, driver driver.Driver) {
driversMu.Lock()
defer driversMu.Unlock()
if driver == nil {
panic("sql: Register driver is nil")
}
if _, dup := drivers[name]; dup {
panic("sql: Register called twice for driver " + name)
}
// 将用户定义的数据库驱动添加到map中
drivers[name] = driver
}
它的第一个参数是数据库驱动的名称,在本例中为mysql。第二个参数是一个结构体,类型为driver.Driver:
type Driver interface {
// Open returns a new connection to the database.
// The name is a string in a driver-specific format.
// Open may return a cached connection (one previously closed), but doing so is unnecessary;
// the sql package maintains a pool of idle connections for efficient re-use.
// The returned connection is only used by one goroutine at a time.
Open(name string) (Conn, error)
}
Driver的底层分析详见:https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/05.1.md
而我们在注册数据库时却并没有调用Register()函数,而是引入了一个数据库驱动的包:github.com/mikespook/mymysql:
// go get 命令安装数据库驱动包
_ "github.com/mikespook/mymysql"
// mymysql包内部:
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
func init() {
Register("SET NAMES utf8")
sql.Register("mymysql", &d)
}
由于Go引入package时,会自动调用自身的init()函数。因此只要我们引入数据库驱动包,就通过它自身的init()函数完成了数据库驱动的注册。所有第三方的数据库驱动包都实现database/sql/driver中定义的接口。
新手都会被这个
_
所迷惑,其实这个就是Go设计的巧妙之处,我们在变量赋值的时候经常看到这个符号,它是用来忽略变量赋值的占位符,那么包引入用到这个符号也是相似的作用,这儿使用_
的意思是引入后面的包名而不直接使用这个包中定义的函数,变量等资源。
调用sql.Open()#
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
第一个参数driverName为数据库驱动名称,在本例中为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.DB结构体类型的指针。
Open()函数并不会真正的连接数据库,甚至不会验证其参数。它只是把后续连接数据库所需的sql.DB设置好,而真正的连接是在后续操作数据库时才建立的。
sql.DB#
type DB struct {
connector driver.Connector
mu sync.Mutex // protects following fields
freeConn []*driverConn
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{
closed bool
stop func() // stop cancels the connection opener and the session resetter.
...
}
sql.DB可以操作数据库,它代表了0个或1个数据库连接池,这些连接由sql包进行维护。sql包会自动的创建和释放这些连接,而且它对于多个goroutine并发的使用的安全的。
最后通过sql.DB的Ping()方法检查数据库连接是否成功。
// Ping检查与数据库的连接是否仍有效,如果需要会创建连接。
func (db *DB) Ping() error
操作数据库#
在数据库kokt中新建一张表userinfo并添加一些记录:
CREATE TABLE `userinfo` (
`uid` INT(10) NOT NULL AUTO_INCREMENT,
`username` VARCHAR(64) NULL DEFAULT NULL,
`department` VARCHAR(64) NULL DEFAULT NULL,
`created` DATE NULL DEFAULT NULL,
PRIMARY KEY (`uid`)
);
查询#
sql.DB的Query()方法可以执行一次select命令,传入SQL语句,返回多行结果(即结构体类型Rows)。第一个参数query表示SQL语句,第二个参数args表示query中的占位参数。
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
Rows拥有的方法主要有:
- func (rs *Rows) Columns() ([]string, error);Columns返回列名。如果Rows已经关闭会返回错误。
- func (rs *Rows) Scan(dest ...interface{}) error;Scan将当前行各列结果填充进dest指定的各个值中。
- func (rs *Rows) Next() bool;Next准备用于Scan方法的下一行结果。如果成功会返回真,如果没有下一行或者出现错误会返回假。
- func (rs *Rows) Close() error;Close关闭Rows,阻止对其更多的列举。
- func (rs *Rows) Err() error;Err返回可能的、在迭代时出现的错误。Err需在显式或隐式调用Close方法后调用。
查询表中departmen字段为marin的记录,并将结果填充到一个结构体切片中:
type result struct {
uid int
username string
department string
created string
}
var results []result
department := "marin"
// 使用?传递参数
rw, err := db.Query("select * from userinfo where department = ?", department)
defer rw.Close()
// 当读取数据完毕后,rw.Next()返回false,退出循环
for rw.Next() {
// temp代表查询得到的一条记录
temp := result{}
err = rw.Scan(&temp.uid, &temp.username, &temp.department, &temp.created)
if err != nil {
log.Fatal(err)
}
// 将查询记录添加到结果切片中
results = append(results, temp)
}
fmt.Println(results)
上述程序输出结果为:
[{4 luffy marin 2020-01-31} {5 ace marin 2020-01-01}]
更新/插入/删除#
sql.DB的Exec()方法可以执行一次命令(包括查询、删除、更新、插入等),传入SQL语句。返回值类型为Result(对已执行的SQL命令的总结),参数args表示query中的占位参数。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
以更新命令为例,将表中username字段为luffy的记录中的department字段更新为pirate:
department := "pirate"
username := "luffy"
_, err = db.Exec("update userinfo set department = ? where username = ?",department, username)
模板#
如果我们需要对一个SQL语句进行重复调用,例如在不同的goroutine中使用相同的SQL语句,那么声明一个模板变量是一个明智的抉择:
type Stmt struct {
// 内含隐藏或非导出字段
}
func (s *Stmt) Exec(args ...interface{}) (Result, error)
func (*Stmt) Query
func (s *Stmt) Close() error
一般先调用sql.DB的Prepare()方法创建一个Stmt结构体作为模板:
func (db *DB) Prepare(query string) (*Stmt, error)
然后调用Stmt的Exec()方法传入占位参数,执行模板中的SQL语句。以插入命令为例,向表userinfo中插入一条新的记录:
username := "joker"
department := "movie"
created := "2020-02-14"
stmt, _ := db.Prepare("insert into userinfo set username=?,department=?,created=?")
defer stmt.Close()
// func (s *Stmt) Exec(args ...interface{}) (Result, error)
stmt.Exec(username, department, created)
作者:koktlzz
出处:https://www.cnblogs.com/koktlzz/p/14249685.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现