修改数据的语句
用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)
执行语句会生成一个sql.Result
可以访问语句元数据的语句:最后插入的ID和受影响的行数。
如果你不关心结果怎么办?如果你只是想执行一个语句并检查是否有错误,但忽略结果呢?以下两个陈述不会做同样的事情吗?
_, err := db.Exec("DELETE FROM users") // 推荐 _, err := db.Query("DELETE FROM users") // 不建议用
答案是不。他们不会做同样的事情,你不应该这样使用 Query()
。该Query()
会返回sql.Rows
,直到它保留了一个数据库连接sql.Rows
被关闭。由于可能有未读数据(例如更多数据行),因此无法使用连接。在上面的例子中,连接永远不会再被释放。垃圾收集器net.Conn最终会为您关闭底层,但这可能需要很长时间。此外,database / sql包会持续跟踪其连接池中的连接,希望在某个时候释放连接,以便连接可以再次使用。因此,这种反模式是耗尽资源的好方法(例如,连接太多)。
使用事务
在Go中,事务本质上是一个保留到数据存储的连接的对象。它可以让你完成迄今为止所看到的所有操作,但保证它们将在相同的连接上执行。
你开始一个事务db.Begin()
,并用其Commit()
或Rollback()
关闭 方法上产生的Tx
变量。在封面下,Tx
从池中获取连接,并保留该连接仅用于该事务。将Tx
地图上的方法一对一地分配给您可以在数据库本身上调用的方法,比如Query()
等等。
在事务中创建的已准备语句仅限于该事务。
你不应该混用事务相关的函数,比如Begin()
和Commit()
SQL语句,比如BEGIN
和COMMIT
SQL代码。坏事可能会导致:
- 这些
Tx
对象可以保持打开状态,从池中保留一个连接而不返回它。 - 数据库的状态可能与代表它的Go变量的状态不同步。
- 你可能会认为你正在一个事务中的单个连接上执行查询,但实际上Go已经无形地为你创建了多个连接,而某些语句不是事务的一部分。
当你在一个事务中工作时,你应该小心不要调用这个Db
变量。将您所有的电话都转到Tx
您创建的变量上db.Begin()
。这Db
不是一个交易,只是一个交易Tx
。如果您打电话给其他人db.Exec()
或类似人员,那么这些情况将会发生在您的交易范围之外,并在其他连接上发生。
如果您需要处理修改连接状态的多条语句,则Tx
即使您不希望事务本身也需要一条语句。例如:
- 创建仅对一个连接可见的临时表。
- 设置变量,比如MySQL的
SET @var := somevalue
语法。 - 更改连接选项,如字符集或超时。
如果您需要执行上述任何操作,则需要将您的活动绑定到单个连接,而在Go中执行此操作的唯一方法是使用 Tx
。