代码之家  ›  专栏  ›  技术社区  ›  Lucian Thorr

Prepared Statements的结果是否因初始化位置而异?

  •  0
  • Lucian Thorr  · 技术社区  · 11 月前

    根据语句是否与事务在同一连接中准备,准备好的语句查询在查询时是否会(或不会)考虑未提交的更改?

    例如

    // Open, prepare statement, close
    sqlite3* db = nullptr;
    sqlite3_open(filename, &db);
    sqlite3_stmt* count_stmt = nullptr;
    sqlite3_prepare_v3(db, "SELECT count(*) FROM names", -1, SQLITE_PREPARE_PERSISTENT, & count_stmt, nullptr));
    sqlite3_close(db);
    // Then open and start transaction and run prepared statement
    sqlite3_open(filename, &db);
    sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, nullptr, &err);
    sqlite3_exec(db, "INSERT INTO names (name) VALUES ('John')", nullptr, nullptr, &err);
    sqlite3_step(count_stmt);
    int count = sqlite3_column_int(count_stmt, 0);
    sqlite3_close(db);
    REQUIRE(count == 1); // fails because transaction wasn't commited
    

    但是,如果我在事务使用的同一连接中准备语句,那么在提交之前,结果将考虑事务。

    // Open, prepare statement, begin transaction and query
    sqlite3* db = nullptr;
    sqlite3_open(filename, &db);
    sqlite3_stmt* count_stmt = nullptr;
    sqlite3_prepare_v3(db, "SELECT count(*) FROM names", -1, SQLITE_PREPARE_PERSISTENT, & count_stmt, nullptr));
    sqlite3_exec(db, "BEGIN TRANSACTION", nullptr, nullptr, &err);
    sqlite3_exec(db, "INSERT INTO names (name) VALUES ('John')", nullptr, nullptr, &err);
    sqlite3_step(count_stmt);
    int count = sqlite3_column_int(count_stmt, 0);
    sqlite3_close(db);
    REQUIRE(count == 1); // passes even though transaction hasn't been committed yet
    
    1 回复  |  直到 11 月前
        1
  •  4
  •   Remy Lebeau    11 月前

    您的代码中有一个错误:很明显,在数据库关闭后,您正在使用与数据库连接相关联的sqlite准备语句!

    这在逻辑上也是错误的!

    但为什么它没有导致崩溃?或者为什么它看起来有效? 来自 sqlite3_close docs :

    如果数据库连接与未定义的已准备关联 语句、BLOB处理程序和/或未完成的sqlite3_backup对象 那么sqlite3_close()将保持数据库连接打开 返回SQLITE_BUSY。如果调用sqlite3_close_v2()时带有unfinalized 准备好的语句、未关闭的BLOB处理程序和/或未完成的 sqlite3_backups,它无论如何都会返回SQLITE_OK,但不是 立即解除分配数据库连接,它会标记 数据库连接为不可用的“僵尸”,并安排 准备完毕后自动解除分配数据库连接 语句完成,所有BLOB句柄关闭,所有备份 已经完成。

    这意味着,当您在完成(关闭)与之相关联的准备好的语句之前关闭了第一个连接时,该连接没有关闭!在关闭语句之前,它仍然是活动的,当执行语句时,它在第一个连接上执行,而不是在第二个连接上!

    为什么sqlite会有这种尴尬的行为?从文档中:

    sqlite3_close_v2()接口旨在与主机一起使用 垃圾收集的语言,以及其中的顺序 析构函数的调用是任意的。

    但为什么它在第一个例子中失败了,而在第二个例子中却没有?好吧,即使在事务提交之前,您的应用程序也应该能够检查事务的执行语句的结果,这就是第二个示例成功的原因。

    对于第一个示例:prepared语句和事务与不同的连接相关联,因此要么行为未定义,要么prepared声明无法访问第二个连接中的事务结果。

    编辑:在连接上的事务内部,只有该连接可以在提交前看到该事务的结果,而在提交后,所有其他连接都可以看到其结果。这可以解释为什么在第一个示例中,第一个连接上准备好的语句在第二个连接上没有看到未提交事务的结果。

    无论如何,在关闭连接本身之前,必须始终关闭与数据库连接关联的所有资源。从文档中:

    理想情况下,应用程序应完成所有准备好的语句,关闭 所有BLOB句柄,并完成所有关联的sqlite3_backup对象 在尝试关闭sqlite3对象之前,先使用该对象。