Различаются ли результаты подготовленных операторов в зависимости от того, где они инициализируются?

Ожидается ли, что запросы подготовленных операторов будут (или не будут) учитывать незафиксированные изменения при запросе, в зависимости от того, подготовлен ли оператор в том же соединении, что и транзакция?

Например:

// 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

Подготовленный оператор выполняется с использованием того соединения, в котором он был подготовлен. Если вы запускаете транзакцию в другом соединении, подготовленный оператор не будет выполнен в этой транзакции.

Barmar 01.07.2024 21:14

Первый фрагмент просто неправильный. count_stmt может быть выполнено только при первом подключении, но вы закрыли его перед выполнением.

Barmar 01.07.2024 21:24

Это может быть неправильно, как «плохо», но не так, как «недействительно». sqlite3_step(count_stmt); вернет результаты и подсчитает строки, которые существовали в таблице до начала транзакции.

Lucian Thorr 01.07.2024 21:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
65
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем коде есть ошибка: вы явно используете подготовленный оператор 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 перед попыткой закрыть объект.

Спасибо за четкое объяснение. Я обнаружил такое поведение при тестировании перехода от одного длительного соединения с подготовленными операторами к однократному созданию базы данных и подготовленных операторов и открытию/закрытию соединений по мере необходимости. Я только что прочитал, что это рекомендовано, но здесь это противоречит этому, если только затраты на подготовку заявлений не будут незначительными.

Lucian Thorr 01.07.2024 22:39

Другие вопросы по теме