Ожидается ли, что запросы подготовленных операторов будут (или не будут) учитывать незафиксированные изменения при запросе, в зависимости от того, подготовлен ли оператор в том же соединении, что и транзакция?
Например:
// 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
Подготовленный оператор выполняется с использованием того соединения, в котором он был подготовлен. Если вы запускаете транзакцию в другом соединении, подготовленный оператор не будет выполнен в этой транзакции.
Первый фрагмент просто неправильный. count_stmt
может быть выполнено только при первом подключении, но вы закрыли его перед выполнением.
Это может быть неправильно, как «плохо», но не так, как «недействительно». sqlite3_step(count_stmt);
вернет результаты и подсчитает строки, которые существовали в таблице до начала транзакции.
В вашем коде есть ошибка: вы явно используете подготовленный оператор sqlite, связанный с подключением к базе данных после закрытия базы данных!
Это тоже логически неверно!
Но почему это не вызвало сбоев? Или почему это работает? Из документации sqlite3_close:
Если подключение к базе данных связано с незавершенным подготовленным операторы, обработчики BLOB и/или незавершенные объекты sqlite3_backup тогда sqlite3_close() оставит соединение с базой данных открытым и верните SQLITE_BUSY. Если sqlite3_close_v2() вызывается с незавершенным подготовленные операторы, незакрытые обработчики BLOB и/или незавершенные sqlite3_backups, он все равно возвращает SQLITE_OK, но вместо немедленное освобождение соединения с базой данных отмечает соединение с базой данных как непригодный для использования «зомби» и принимает меры для автоматически освободить соединение с базой данных после того, как все подготовлено операторы финализированы, все дескрипторы BLOB закрыты, а все резервные копии Закончив.
Это означает, что когда вы закрыли первое соединение до финализации (закрытия) связанного с ним подготовленного оператора, соединение не было закрыто! Он все еще активен, пока оператор не будет закрыт, а когда оператор выполняется, он выполняется при первом соединении, а не при втором!
Почему такое неловкое поведение sqlite? Из документов:
Интерфейс sqlite3_close_v2() предназначен для использования с хостом языки, которые подлежат сборке мусора, и где порядок, в котором деструкторы называются произвольно.
Но почему это не удается в первом примере, а не во втором? Что ж, ваше приложение должно иметь возможность проверять результаты выполненных операторов транзакции еще до того, как транзакция будет зафиксирована. Вот почему второй пример успешен.
В первом примере: подготовленный оператор и транзакция связаны с разными соединениями, поэтому либо поведение не определено, либо подготовленный оператор не может получить доступ к результату транзакции во втором соединении.
Обновлено: внутри транзакции в соединении только это соединение может видеть результаты этой транзакции до ее фиксации, а после фиксации все остальные соединения могут видеть ее результаты. Это может объяснить, почему в первом примере подготовленный оператор при первом соединении не видит результат незафиксированной транзакции при втором соединении.
В любом случае, вы всегда должны закрывать все ресурсы, связанные с подключением к базе данных, прежде чем закрывать само соединение. Из документов:
В идеале приложения должны доработать все подготовленные выписки, закрыть все дескрипторы BLOB и завершить все связанные объекты sqlite3_backup. с объектом sqlite3 перед попыткой закрыть объект.
Спасибо за четкое объяснение. Я обнаружил такое поведение при тестировании перехода от одного длительного соединения с подготовленными операторами к однократному созданию базы данных и подготовленных операторов и открытию/закрытию соединений по мере необходимости. Я только что прочитал, что это рекомендовано, но здесь это противоречит этому, если только затраты на подготовку заявлений не будут незначительными.
Связанный: Можно ли использовать оператор, подготовленный с помощью sqlite3_prepare_v2, с другим соединением?