【问题标题】:In sqlite3, is reset required after a failed call to step?在 sqlite3 中,步骤调用失败后是否需要重置?
【发布时间】:2019-02-25 14:33:27
【问题描述】:

在调用sqlite3_step() 失败后,是否需要在准备好的语句上调用sqlite3_reset()?我正在使用 sqlite3 版本 3.23.1。我准备好的语句的生命周期如下:

  1. 在我的应用程序开始时,我全局执行sqlite3_prepare_v2() 并在应用程序的生命周期内保持准备好的语句的句柄可用。
  2. 当我准备好进行查询时,我调用sqlite3_bind_*() 函数之一,然后对该语句执行sqlite3_step(),直到返回SQLITE_ROW 以外的其他内容。
  3. 然后执行下面的代码来重置语句。

这是我调用sqlite3_step() 后发生的部分代码。请注意,变量resultCode 保存最后一次调用sqlite3_step() 的返回值。

   if (resultCode == SQLITE_DONE || resultCode == SQLITE_ROW)
   {
      if (sqlite3_reset(m_statement) != SQLITE_OK)
      {
         LogDbFailure(*m_db, "sqlite3_reset()");
      }
   }
   else
   {
      LogDbFailure(*m_db, "sqlite3_step()");
      success = false;
   }

请注意,如果对 step 的调用失败,我不会进行重置。 Google 上的文档或搜索结果中没有任何内容表明必须在失败时调用 sqlite3_reset()。事实上,documentation 声明失败后调用sqlite3_reset() 也会失败:

如果最近对准备好的语句 S 的 sqlite3_step(S) 调用指示错误,则 sqlite3_reset(S) 返回适当的错误代码。

读到这里让我想到如果步骤失败我不应该调用重置函数。

谁能澄清一下?请注意,在我的情况下,sqlite3_step()SQLITE_BUSY 而失败。我正在使用 WAL 日志模式。一旦准备好的语句上的步骤失败,当我调用sqlite3_step() 时,该准备好的语句将永远处于忙碌状态。之后调用sqlite3_bind_*() 会返回sqlite3_bind_int64() failed (21): bad parameter or other API misuse(日志格式是我自己的,但21 是错误代码),这让我认为在失败情况下应该调用reset,因为所有错误似乎表示数据库正忙,因为准备好的语句由于缺少重置而卡在事务中间。

【问题讨论】:

    标签: c sqlite


    【解决方案1】:

    请注意,如果对 step 的调用失败,我不会进行重置。什么都没有 Google 上的文档或搜索结果表明 sqlite3_reset() 必须在失败时调用。

    不,不是特别,而是the docs for sqlite3_reset()

    调用 sqlite3_reset() 函数来重置准备好的语句 对象恢复到初始状态,准备重新执行。

    你添加,

    事实上,文档 指出在失败后调用sqlite3_reset() 也会失败:

    如果对准备好的语句 S 的最近一次调用 sqlite3_step(S) 指示错误,则 sqlite3_reset(S) 返回一个 相应的错误代码。

    不,您误解了这一点。 “返回适当的错误代码”和“将失败”之间有一个重要的区别。从the docs for sqlite3_step() 的摘录中考虑,这应该更清楚:

    在遗留接口中,sqlite3_step() API 总是返回一个 通用错误代码,SQLITE_ERROR,出现除以下错误之外的任何错误 SQLITE_BUSY 和 SQLITE_MISUSE。您必须调用 sqlite3_reset() 或 sqlite3_finalize() 以查找特定错误代码之一 更好地描述了错误。

    虽然sqlite3_step() 的行为仅适用于旧接口,而不适用于 V2 接口,但它解释了为什么sqlite3_reset() 的返回值报告的是先前对sqlite3_step() 的调用(如果有)的结果,而不是自己的成败。这意味着重置本身不会失败,或者至少不能通过其返回码报告自己的失败。

    读到这里让我觉得也许我不应该调用重置 步骤失败时的函数。

    sqlite3_step() 的文档在这一点上有这样的说法:

    对于 3.6.23.1 及之前的所有 SQLite 版本,调用 在 sqlite3_step() 返回任何内容后需要 sqlite3_reset() 除了 SQLITE_ROW 在任何后续调用之前 sqlite3_step().

    注意:因此在sqlite3_step() 报告错误后调用sqlite3_reset()没有错。文档继续说,

    使用重置准备好的语句失败 sqlite3_reset() 将导致 SQLITE_MISUSE 从 sqlite3_step()。但在 3.6.23.1 版本之后(2010-03-26,sqlite3_step() 在这种情况下开始自动调用 sqlite3_reset() 而不是返回 SQLITE_MISUSE。

    这似乎与您报告的行为不一致,但请注意,

    [...] SQLITE_OMIT_AUTORESET 编译时选项可用于恢复遗留行为。

    因此,您最安全的选择是无条件地重置语句,而不是避免在报告错误后重置它。对于许多 SQLite3 构建而言,这可能是不必要的,但它并没有错或有害,而且对于某些构建是必要的。

    【讨论】:

    • 谢谢。我认为其中一些引用适用于早于我的版本,但也许存在设计缺陷,这些规则仍然会以某种方式影响以后的版本。但是,我认为您的观点是无论免责声明如何安全,都要慷慨地重置。
    • @void.pointer,所有文档引用均来自截至今天的当前文档。它们适用于您的版本。界面之所以如此,是有历史原因的,但设计带有历史包袱并不意味着您可以期望它的行为与记录的不同。并且在文档允许改变行为的可能性的范围内,是的,我的观点是您的代码应该适应 所有 记录的可能性。
    • 我知道当前文档适用于我的版本,但我说的是向后兼容性说明。例如,它说在 3.6.23.1 之后,现在逐步调用重置。当我设计我的系统时,我省略了显式调用来支持这一点。像这样的东西具有误导性,因为根据我正在使用的文档和版本,我没有看到我期望的行为。我可能会误解文档,因为老实说它非常令人困惑,但最终我会调用 reset 并完成它。边缘情况太多了。
    • @void.pointer,文档说从 3.6.23.1 开始,sqlite3_step() 会自动调用重置,除了原来的行为(不自动重置)可以通过编译时选项来选择。那将是 SQLite 编译的选项,而不是您的程序的选项。那么,我的观点是,除非您可以控制 SQLite 构建,或者可以验证它的这个细节,否则最安全的选择是不依赖自动重置。
    • 此外,SQLite 文档将自动重置作为后备,它的引入证明了未能重置构成编程错误的事实。从这个意义上说,尽管并非所有 SQLite 构建都需要在错误后重置,但它始终是预期和适当的。
    猜你喜欢
    • 2012-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-10
    相关资源
    最近更新 更多