Modifying Data With db.Exec()
到目前为止,使用的都是db.Query()和db.QueryRow(),但是你也看到过db.Exec()。这个方法在执行语句中的作用不返回行结果。下边是例子:
res, err := db.Exec("DELETE FROM hello.world LIMIT 1") if err != nil {
log.Fatal(err) }
rowCnt, err := res.RowsAffected() if err != nil {
log.Fatal(err) }
log.Println("deleted rows:", rowCnt)
db.Exec()会返回一个sql.Result对象,你可以用它来获取受影响的行数,就像上面代码里的那样。你也可以用它来获取最后一行的自增id,尽管对它的支持因驱动程序和数据库而异。例如,在PostgreSQL中,你应该使用INSERT RETURNING和db.QueryRow()来获取所需的值作为结果集。
db.Exec()和db.Query()有个很重要的不同,这不是故弄玄虚。在本书的前面提到过,db.Exec()会立即释放连接给连接池,而db.Query()直到调用rows.Close()之前都会保留连接。下面的代码忽略了返回的rows,这样会导致问题:
_, err := db.Query("DELETE FROM hello.world LIMIT 1")
问题是虽然从方法返回的第一个值被分配给_变量,并且在此之后程序无法访问,但它依然被分配了,具有所有常见的后果。它不会消失,直到促发垃圾回收时。更糟糕的是,绑定到它的连接永远不会返回到连接池。这样很容易导致连接泄漏,让服务无连接可用。
除了上边的内容,你还需要了解一点关于Result的细节。Go保证用于创建Result的数据库连接与用于LastInsertId()和RowsAffected()的连接相同, 它被从池中取出用于这些操作并被锁定。但除此之外,它是一种接口类型,确切的行为将取决于底层数据库和驱动程序提供的实现。举个例子:
-
MySQL可以使用BIGINT UNSIGNED作为自动增量列,因此最后插入的行的ID可能太大而不适合LastInsertId()定义的返回类型int64。
-
我们更喜欢的是MySQL驱动程序不会额外往返数据库,以找出最后插入的值和受影响的行数。此信息以有线协议从服务器返回,并存储在结构中,因此不需要往返操作。(连接仍然从池中取出并锁定,然后放回,即使它没有被使用。这是由database/sql完成的,而不是驱动程序。因此,即使此函数不访问数据库,如果其连接繁忙,它仍可能阻止等待。)
-
LastInsertId()和RowsAffected()是否返回错误是由驱动程序决定的。比如说在MySQL的驱动程序中,他就不会返回错误。但是,你不应该依赖于这样的驱动程序特定的细节。坚持database/sql公开承诺的接口声明:对返回错误的函数进行错误检查。
-
这些功能的某些行为因数据库或实现而异。例如,假设数据库驱动程序提供RowsAffected(),但通过查询底层数据库来实现它(比如说在MySQL中额外调用SELECT LAST_INSERT_ID()而不是直接读取协议中定义好的值)。如上所述,将使用原始连接,在某种程度上行为将是正确的,但如果在此期间已对该连接进行了其他工作,该怎么办?你会受到竞争条件的影响。 在这个领域,你需要了解正在使用的实际实现。
一般来说,我们见过和使用过的数据库驱动程序都得到了很好的实现,大多数时候你不需要担心这些细节。但是我们更希望你能够了解这些细节。
现在让我们看看如何使用预处理语句(prepared statements).
网友评论