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

修改数据的语句

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

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)

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

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

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

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

使用事务

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

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

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

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

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

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

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

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

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

转自:https://learnku.com/docs/go-database-sql/9478.md

最后编辑: kuteng  文档更新时间: 2021-11-19 09:18   作者:kuteng