MyMySQL 的 database/sql 接口使用

mikespook · 2014-10-09 16:15:45 · 3901 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2014-10-09 16:15:45 的文章,其中的信息可能已经有所发展或是发生改变。

最近看到不少朋友对 Golang 中操作 MySQL 数据库有疑问,那么就此内容给大家分享一下吧。

MyMySQL 的原作者是来自波兰的 ziutek,他根据 MySQL 的协议标准使用 Golang 实现了 MyMySQL 包。根据他的介绍,这个包可以用在 MySQL 4.1 或更高版本上,并且在 5.0、5.1 版本上经过项目的实际验证。

只要用 MySQL 做过项目的朋友一定遇到过编码问题,set names 几乎成为了中文环境下使用 MySQL 的标配。那么 Golang 也不能例外。不过由于 database/sql 使用了随机的连接池,且未提供任何方法让所有连接都执行某个操作,于是这个麻烦事儿就落到了驱动的肩上。对于中文什么的,大老外一向不怎么在意。我提交了这个 issue 给 ziutek,不过对于他最终实现的方案不怎么满意。需要额外编写godrv.Register 来对编码进行设置。

同时当连接空闲时间超出服务器的 wait_timeout 时,会出现 broken pipe。对于这个问题,ziutek 的建议是使用 MyMySQL 的 autorc,但是 godrv 并没有基于 autorc 实现 database/sql。换句话说,要解决这个错误,只能使用 MyMySQL 原始接口。

鉴于这些问题,我只能 fork 了 ziutek 的项目,对 MyMySQL 打上相应的补丁。主要是针对编码设置进行了改进,并顺便实现了 keepalive 功能。

本文主要介绍 fork 的版本的使用。

在 fork 的版本中,还有一个改进就是统一了 go get 的安装接口,无需再逐一安装 MyMySQl 的子包,只需要

go get github.com/mikespook/mymysql

即可完成安装。

关于使用,还是用代码说话吧,完整代码看这里。为了通用,这里只介绍使用 database/sql 接口的使用。在实际项目中,我也建议大家尽量使用该接口,这样在最大可用性保障下保持兼容性。

import(
    "log"
    "database/sql"
    _ "github.com/mikespook/mymysql/godrv"
)

使用 MyMySQL 的 database/sql 接口要导入 godrv 包。在这个包的 init 函数中自动注册了 database/sql 的驱动,所以导入后无需再使用。

db, err := sql.Open("mymysql", "tcp://127.0.0.1:3306/test/root/xxiyy?charset=utf8&keepalive=1200")
if err != nil {
    log.Fatal(err)
}
defer db.Close()

使用 mymysql 驱动打开一个 sql.DB 连接。dsn 连接串是我对 ziutek 的 MyMySQL 的主要改进。支持多种连接方式:

  • 使用 tcp 协议:[tcp://addr/]dbname/user/password[?params]
  • 使用 unix sock:[unix://sockpath/]dbname/user/password[?params]

其中 tcp 协议的 addr 必须是含有主机名或 ip,且包含又分号分隔的端口号的字符串。如 localhost:3306、192.168.3.2:3307。unix sock 协议中的 sockpath 必须是 MySQL 的 sock 文件的绝对路径。

在上述 dsn 中,方括号内的是可选的内容,可省略。当协议信息(第一个方括号内)未指定时,使用 tcp://127.0.0.1:3306/ 作为默认值。

params 部分是用于当前数据库驱动的参数设置,当前来说,只有两个可设置的参数:

  • charset:用于 ‘set names’ 设置连接编码。
  • keepalive:每 keepalive 秒向服务器发送 PING。

需要特别强调的是,如果密码含有斜线(/),由于解析规则的缘故,需要用星号(*)代替。如果密码含有星号(*),则需要用两个星号(**)代替。例如:

  • 原密码 [pass/wd],在 dsn 中应写为 [pass*wd]。
  • 原密码 [pass*wd],在 dsn 中应写为 [pass**wd]。
stmt, err := db.Prepare("insert into `test` (`key`, `value`) values (?, ?)")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close()
rslt, err := stmt.Exec("name", "foobar")
if err != nil {
    log.Fatal(err)
}
if a, err := rslt.RowsAffected(); err != nil {
    log.Print(err)
} else {
    log.Printf("[INS]Affected rows=%d", a)
}
if id, err := rslt.LastInsertId(); err != nil {
    log.Print(err)
} else {
    log.Printf("[INS]Last insert id=%d", id)
}

在 database/sql 接口里,有多种办法执行某个 SQL 语句。出于安全考虑,强烈建议大家不要使用字符串方式直接拼接 SQL 语句进行执行,如果可能,在大多数其他接口中都会出现的 Prepare 方法是首选。执行完 SQL 语句后,可以通过 sql.Result 得到这次执行所影响的行数,和最后插入的 Id 值。如果表中没有使用 autoincrement 作为主键,那么这个 Id 值永远为 0。

rows, err := db.Query("select * from `test`")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
    var k, v string
    rows.Scan(&k, &v)
    log.Printf("[ROWS]key=%s, value=%s", k, v)
}

查询一个二维表,从中取得多行数据使用 Query 方法。需要注意的是 sql.Rows 的 Scan 方法接收的参数必须是指针。也就是说存放数据的地址要在 Scan 调用前准备好。Scan 内部使用了反射来识别参数类型并进行赋值,也可以传递一个 []interface{}。这时必须这样调用:rows.Scan(a…) 才能得到预期结果。

row := db.QueryRow("select * from `test` where `key` = ?", "name")
var k, v string
row.Scan(&k, &v)
log.Printf("[ROW]key=%s, value=%s", k, v)

对于有些如用主键进行的查询,仅仅会读出一行数据。那么可以用 QueryRow 进行更加简化的读取。

rslt, err = db.Exec("delete from `test`")
if a, err := rslt.RowsAffected(); err != nil {
    log.Print(err)
} else {
    log.Printf("[DEL]Affected rows=%d", a)
}
if id, err := rslt.LastInsertId(); err != nil {
    log.Print(err)
} else {
    log.Printf("[DEL]Last insert id=%d", id)
}

对于没有拼接 SQL 需要的操作,可以直接在 sql.DB 对象上调用 Exec 方法。返回的 sql.Result 与 sql.Stmt 的 Exec 方法一致。

关于基础的使用,大致就这些内容。database/sql 也支持事物,这部分信息大家可以参考其文档进行学习。


有疑问加站长微信联系(非本文作者)

本文来自:mikespook 的博客

感谢作者:mikespook

查看原文:MyMySQL 的 database/sql 接口使用

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3901 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传