扫码订阅《 》或入驻星球,即可阅读文章!

GOLANG ROADMAP

阅读模式

  • 沉浸
  • 自动
  • 日常
首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
author-avatar

GOLANG ROADMAP


首页
Go学习
  • Go学院

    • Go小课
    • Go小考
    • Go实战
    • 精品课
  • Go宝典

    • 在线宝典
    • B站精选
    • 推荐图书
    • 精品博文
  • Go开源

    • Go仓库
    • Go月刊
  • Go下载

    • 视频资源
    • 文档资源
Go求职
  • 求职服务

    • 内推互助
    • 求职助力
  • 求职刷题

    • 企业题库
    • 面试宝典
    • 求职面经
Go友会
  • 城市
  • 校园
推广返利 🤑
实验区
  • Go周边
消息
更多
  • 用户中心

    • 我的信息
    • 推广返利
  • 玩转星球

    • 星球介绍
    • 角色体系
    • 星主权益
  • 支持与服务

    • 联系星主
    • 成长记录
    • 常见问题
    • 吐槽专区
  • 合作交流

    • 渠道合作
    • 课程入驻
    • 友情链接
  • Go SQL数据库教程

    • 1.概述
    • 2.导入数据库驱动
    • 3.连接数据库
    • 4.获取数据集
    • 5.更新数据和事务
    • 6.使用预处理语句
    • 7.处理错误
    • 8.处理 NULL
    • 9.未知的列
    • 10.sql 连接池
    • 11.注意事项

扫码订阅《 》或入驻星球,即可阅读文章!

5.更新数据和事务


GOLANG ROADMAP

现在,我们准备看看如何修改数据和处理事务。如果您习惯于使用 「statement」对象来获取行以及更新数据的语言进行编程,这种区别可能看起来是人为的,但是在 Go 语言中,这种区别是有重要原因的。

# 修改数据的语句

使用 Exec(),最好是使用预编译语句,来完成 INSERT,UPDATE,DELETE 或其他不返回行的语句。以下示例显示如何插入行并检查有关该操作的元数据:

stmt, err := db.Prepare("INSERT INTO users(name) VALUES(?)")
if err != nil {
	log.Fatal(err)
}
res, err := stmt.Exec("Dolly")
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)
}
log.Printf("ID = %d, affected = %d\n", lastId, rowCnt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行该语句将产生一个 sql.Result,它提供对语句元数据的访问:最后插入的 ID 和受影响的行数。

如果您不关心执行结果,该怎么办?如果您只想执行一个语句并检查是否有错误,而忽略结果呢?下面的两个语句不是做了同样的事情吗?

_, err := db.Exec("DELETE FROM users")  // OK
_, err := db.Query("DELETE FROM users") // BAD
1
2

答案是 no。它们 不会 做同样的事情,也请您永远不要这样使用 Query()。Query() 将返回一个 sql.Rows,它将保留数据库连接,直到 sql.Rows 关闭。由于可能存在未读数据 (例如,更多的数据行),因此无法使用该连接。在上面的示例中,连接将 永远 不会被释放。垃圾收集器最终将为您关闭底层 net.Conn,但这可能需要很长时间。此外,database/sql 包会在连接池中继续跟踪连接,希望您在某个时候释放它,以便可以再次使用该连接。因此,此反模式是耗尽资源 (例如,连接过多) 的好方法。

# 使用事务

在 Go 中,事务本质上是一个保留与数据存储区连接的对象。它可以让您执行到目前为止所看到的所有操作,但可以保证它们将在同一连接上执行。

您可以通过调用 db.Begin() 开启事务,然后使用该函数生成的 Tx 对象上的 Commit() 或 Rollback() 方法来结束事务。在后台,Tx 从池中获得连接,并将其保留,以仅用于该事务。Tx 上的方法一对一映射到您可以在数据库本身上调用的方法,例如 Query() 等。

在事务中创建的预处理语句专门绑定到该事务。有关更多信息,请参见 预处理语句 (opens new window)。

您不应该在 SQL 代码中混合使用与事务相关的函数(例如 Begin() 和 Commit())和 SQL 关键字(例如 BEGIN 和 COMMIT)。这可能会导致不良后果:

  • Tx 数据库对象可能保持打开状态,保留池中的连接而不返回它。
  • 数据库的状态可能与代表它的 Go 变量的状态不同步。
  • 您可能会认为您正在事务内部的单个连接上执行查询,而实际上 Go 已经为您创建了多个不可见的连接,并且某些语句不是该事务的一部分。

在事务内部进行操作时,应注意不要调用 db 变量。进行所有对您使用 db.Begin() 创建的 Tx 变量的调用。db 不在事务中,只有 Tx 对象在事务中。如果您进一步调用 db.Exec() 或类似方法,则这些调用将在事务范围之外发生在其他连接上。

如果您需要使用多个修改连接状态的语句,即使您本身不需要事务,也需要 Tx。例如:

  • 创建临时表,仅对一个连接可见。
  • 设置变量,例如 MySQL 的 SET @var:= somevalue 语法。
  • 更改连接选项,例如字符集或超时。

如果您需要执行上述任何操作,则需要将活动绑定到单个连接,而 Go 中唯一的方法是使用 Tx。

  • 修改数据的语句
  • 使用事务