尽管 database/sql 在您一旦习惯了就觉得很简单,但您可能会对它支持的用例的精妙之处感到惊讶。这在 Go 的核心库中很常见。

资源耗尽

如本网站所述,如果您不按预期使用 database/sql,肯定会给自己带来麻烦。通常是通过消耗某些资源或阻止其有效重用而造成的:

  • 打开和关闭数据库可能会导致资源耗尽。
  • 无法读取所有行或使用 rows.Close() 保留来自池的连接。
  • 对不返回行的语句使用 Query() 将保留来自池的连接。
  • 不了解 预处理语句 是如何工作的可能会导致大量额外的数据库活动。

uint64 值过大

这是一个令人惊讶的错误。如果设置了大的无符号整数的高位,则不能将其作为参数传递给语句:

_, err := db.Exec("INSERT INTO users(id) VALUES", math.MaxUint64) // Error

这将抛出错误。如果您使用 uint64 值,请务必小心,因为它们起初可能很小并且可以正常工作,但是随着时间的推移会增加并开始引发错误。

连接状态不匹配

有些事情会改变连接状态,这可能会引起问题,原因有两个:

  1. 某些连接状态,例如您是否在事务中,应该通过 Go 类型来处理。
  2. 您可能会假设您的查询在单个连接上运行,而事实并非如此。

例如,使用 USE 语句设置当前数据库是许多人通常要做的事情。但是在 Go 语言中,它只会影响运行它的连接。除非您处于事务中,否则您认为在该连接上执行的其他语句实际上可能在从池中获得的不同连接上运行,因此它们不会看到这种更改的影响。

此外,在更改连接之后,它将返回池,并可能污染某些其他代码的状态。这也是为什么不应该直接将 BEGIN 或 COMMIT 语句作为 SQL 命令发出的原因之一。

特定于数据库的语法

database/sql API 提供了面向行的数据库的抽象,但是特定的数据库和驱动程序的行为 和/或 语法可能有所不同,例如 预处理语句占位符

多个结果集

Go 驱动程序不以任何方式支持单个查询的多个结果集,而且似乎也没有计划这样做,虽然有 功能请求 支持批量操作,例如批量复制。

这意味着,一个返回多个结果集的存储过程将无法正常工作。

调用存储过程

调用存储过程是特定于驱动程序的,但是在 MySQL 驱动程序中目前不支持。您似乎可以通过执行以下命令来调用返回单个结果集的简单过程:

err := db.QueryRow("CALL mydb.myprocedure").Scan(&result) // Error

实际上,这是行不通的。您将收到以下错误:错误 1312:PROCEDURE mydb.myprocedure 无法在给定的上下文中返回结果集。这是因为 MySQL 希望将连接设置为多语句模式,即使是针对单个结果也是如此,而驱动程序当前没有这样做 (请参见 此问题)。

多语句支持

database/sql 没有明确支持多条语句,这意味着其行为取决于后端:

_, err := db.Exec("DELETE FROM tbl1; DELETE FROM tbl2") // Error/unpredictable result

允许服务器根据需要进行解释,包括返回错误,仅执行第一条语句或同时执行这两项。

类似地,也没有办法在事务中批处理语句。事务中的每个语句都必须串行执行,结果中的资源 (比如一行或多行) 必须被扫描或关闭,以便底层连接可以自由地供下一个语句使用。这与不处理事务时的通常行为不同。在该场景中,完全有可能执行查询,循环遍历行,并在循环中对数据库进行查询 (这将发生在一个新的连接上):

rows, err := db.Query("select * from tbl1") // Uses connection 1
for rows.Next() {
    err = rows.Scan(&myvariable)
    // The following line will NOT use connection 1, which is already in-use
    db.Query("select * from tbl2 where id = ?", myvariable)
}

但是事务只绑定到一个连接,所以这对于事务是不可能的:

tx, err := db.Begin()
rows, err := tx.Query("select * from tbl1") // Uses tx's connection
for rows.Next() {
    err = rows.Scan(&myvariable)
    // ERROR! tx's connection is already busy!
    tx.Query("select * from tbl2 where id = ?", myvariable)
}

不过,Go 不会阻止您尝试。因此,如果您试图在第一条语句释放其资源并在其自身清除之前执行另一条语句,则可能会导致连接损坏。这也意味着,事务中的每个语句都会产生一组单独的数据库网络往返行程。

转自:https://learnku.com/docs/go-database-sql/surprises/9484

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