【问题标题】:How to modify rows while iterating through them如何在迭代行时修改行
【发布时间】:2017-09-25 10:43:21
【问题描述】:

我有一个循环遍历一个看起来与此类似的表(为提高可读性而简化了错误处理):

for rows.Next() {
    var (
        id          int
        field2      int
        field1      int
    )
    err = rows.Scan(&id, &field1, &field2)
    chk(err)
    field1 += someFunc()
    field2 += someOtherFunc()
    err = db.Exec(`UPDATE table SET field1 = ?, field2 = ? WHERE id = ?`, field1, field2, id)
    chk(err)
}

如您所见,我想扫描每一行的字段,以某种方式修改它们并更新数据库。 someFuncsomeOtherFunc 返回的值对于每一行都是不同的,并且无法仅从其字段的值中确定。

这段代码不起作用,因为数据库被锁定直到rows 关闭(我正在使用mattn's go-sqlite3 driver)。此外,它效率不高,因为每次更新行时数据库都需要执行查询。我知道我可以使用db.Prepare,然后在完成迭代后执行所有查询,但这会消耗不必要的内存量并且不会缓解效率问题。 我做了一些阅读,似乎游标提供了我正在寻找的功能(我不是 SQL 专家)并且我使用的 SQL 风格(SQLite3)支持它们,但database/sql 似乎没有到。

database/sql 中是否有任何自然的方式可以在迭代行时更新行?

【问题讨论】:

  • 光标仅供阅读。并且您不得更改您正在使用光标阅读的表格。
  • 请原谅我的无知,但这->“数据库被锁定直到rows 被关闭”听起来是错误的......这是真的吗?你能指点我一些特别提到这一点的文件吗?还是您使用的 sql 驱动程序的行为方式以及您使用的是什么驱动程序?
  • @mkopriva 我编辑了问题以包含驱动程序。在我关闭rows 之前,数据库确实被锁定。它也可能是特定于驱动程序的行为,database/sql 的文档中没有任何内容表明它应该以这种方式运行,但也没有任何内容表明它是不允许的。我从未声称这种行为是普遍的。但是,我查看了驱动程序的文档,似乎也没有关于这种行为的内容。
  • 很公平。您的代码是否有机会在事务中运行?我记得 postgres 有类似的问题,但要解决它,我需要做的就是在循环内使用单独的 tx(或连接?我不记得确切)。所以基本上我会有两个连接或其他东西,一个用于选择行,另一个用于更新表格。
  • @mkopriva 是的,这似乎是造成这种情况的驱动程序。我现在发现的最优雅的工作解决方案似乎是在循环之前启动事务,将其用于Exec 调用并在循环完成后提交它。这可能仍然使用比它应该使用的更多的内存,但至少它在代码中看起来更好。我想我会考虑在不久的将来换一个不同的驱动程序。

标签: sql go sqlite


【解决方案1】:

您应该针对事务执行 SELECT 和 UPDATE,而不是句柄。我在我的一个项目中偶然发现了它,现在有这样的代码:

tx, err := dbh.Begin()
...
rows1, err := tx.Query("SELECT id, data FROM cache")
...
for rows1.Next() {
...
   tx.Exec("DELETE FROM cache WHERE id = ?", id)
...
}
tx.Commit()

并且没有更多关于锁定数据库的错误。

【讨论】:

    猜你喜欢
    • 2023-01-31
    • 2016-03-26
    • 2012-03-03
    • 2015-02-05
    • 1970-01-01
    • 2013-05-13
    • 2014-09-17
    • 2020-05-29
    • 2017-12-05
    相关资源
    最近更新 更多