multi_query не работает, когда я использую транзакции mysql с mysqli в php
Вот пример моего кода
$mysqli = new mysqli("localhost", "root", "123", "temp");
var_dump($mysqli);
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$test = $mysqli->multi_query($sql1);
$mysqli->commit();
1 ответ
$mysqli->multi_query($sql1);
$mysqli->commit(); // This will result in a "Commands out of sync; you can't run this command now" error.
Вышеупомянутое идентично:
$mysqli->multi_query($sql1);
$mysqli->query("commit"); // This will result in a "Commands out of sync; you can't run this command now" error.
Что бы вы ни вложили $mysqli->query("...");
, это приведет к "Commands out of sync"
ошибка, даже с простым SELECT 1
;
Причина этой ошибки в том, что ->commit()
операция выполняет один запрос (commit;
). Однако результаты предыдущих запросов не были прочитаны.
Когда сингл query()
Если используется операция, сервер MySQL ответит кадром ответа, который зависит от оператора запроса.
Когда используешь multi_query()
, на уровне протокола связи MySQL происходит следующее:
- Фрейм "Request Set Option" (как показано в Wireshark) отправляется с установленным флагом "multi statement" в положение ON.
- Кадр, содержащий всю строку, переданную в
multi_query()
как запрос. - Сервер MySQL отвечает ответом, который может содержать разные наборы результатов. То же самое и при вызове хранимой процедуры.
Решение 1
Если вы хотите использовать multi_query()
ты должен иметь свой start transaction
/ commit
операции в его составе:
$mysqli = new mysqli("localhost", "root", "123", "temp");
$sql1 = "start transaction;"; // $mysqli->begin_transaction() is a convenience function for simply doing this.
$sql1 .= "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$sql1 .= "commit;"; // $mysqli->commit() is a convenience function for simply doing this.
$mysqli->multi_query($sql1);
/* As in "Solution 2", if you plan to perform other queries on DB resource
$mysqli after this, you must consume all the resultsets:
// This loop ensures that all resultsets are processed and consumed:
do {
$mysqli->use_result();
}
while ($mysqli->next_result());
*/
Решение 2
$mysqli = new mysqli("localhost", "root", "123", "temp");
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$mysqli->multi_query($sql1);
// This loop ensures that all resultsets are processed and consumed:
do {
$mysqli->use_result();
}
while ($mysqli->next_result());
// Now that all resultsets are processed, a single query `commit;` can happen:
$mysqli->commit();
Справочник MySQL: "Команды не синхронизированы".
Вы не должны использовать несколько запросов. Перепишите ваш код следующим образом
$mysqli->begin_transaction();
$mysqli->query("insert into test (Name) values ('pratik5')");
$mysqli->query("insert into test (Name) values ('pratik6')");
$mysqli->commit();
или, для реальных вставок,
$mysqli->begin_transaction();
$stmt = $mysqli->prepare("insert into test (Name) values (?)");
$stmt->bind_param("2", $name);
$name = 'pratik5';
$stmt->execute();
$name = 'pratik6';
$stmt->execute();
$mysqli->commit();
Прежде всего, в показанном вами примере вам не следует использоватьmulti_query()
. У вас есть два отдельных оператора, которые должны выполняться отдельно. См . Ответ вашего здравого смысла
multi_query()
следует использовать редко. Его следует использовать только в ситуациях, когда у вас уже есть строка, состоящая из нескольких запросов, которой вы абсолютно доверяете. Никогда не разрешайте ввод переменных вmulti_query()
!
Зачем commit()
не работает после multi_query()
?
По правде говоря, MySQL выдает ошибку commit()
, но mysqli не может выдать ошибку как исключение. Я не знаю, ошибка это или техническое ограничение. Вы можете увидеть ошибку, если проверите вручную$mysqli->error
свойство. Вы должны увидеть следующее сообщение об ошибке:
Команды не синхронизированы; вы не можете запустить эту команду сейчас
Однако исключение генерируется правильно после вызова use_result()
или store_result()
.
$mysqli->multi_query(/* SQLs */);
$mysqli->commit();
$r = $mysqli->use_result(); // Uncaught mysqli_sql_exception: Commands out of sync; you can't run this command now
Причина, по которой MySQL сообщает, что команды не синхронизированы, заключается в том, что вы можете выполнять только одну команду за раз в каждом сеансе MySQL. Однако,multi_query()
отправляет всю строку SQL в MySQL за одну команду, и пусть MySQL выполняет запросы асинхронно. Сначала PHP будет ждать завершения выполнения первого набора результатов, создающего не-DML-запрос (не INSERT, UPDATE или DELETE), прежде чем элемент управления будет передан обратно в сценарий PHP. Это позволяет MySQL запускать остальную часть SQL на сервере и буферизовать результаты, в то время как вы можете выполнять некоторые другие операции в PHP и собирать результаты позже. Вы не можете запустить другую команду SQL в том же соединении, пока не переберете все результаты предыдущих асинхронных запросов.
Как указано в другом ответе, commit()
попытается выполнить другую команду в том же соединении. Вы получаете ошибку рассинхронизации, потому что вы просто не завершили обработкуmulti_query()
команда.
Блокирующий цикл MySQL i
За каждым асинхронным запросом должен следовать блокирующий цикл в mysqli. Блокирующий цикл будет перебирать все выполненные запросы на сервере и получать результаты один за другим, которые затем можно обработать в PHP. Вот пример такого цикла:
do {
$result = $mysqli->use_result();
if ($result) {
// process the results here
$result->free();
}
} while ($mysqli->next_result()); // Next result will block and wait for next query to finish
$mysqli->store_result(); // Needed to fetch the error as exception
У вас всегда должен быть цикл блокировки, даже если вы знаете, что запросы не будут давать никаких наборов результатов.
Решение
Держись подальше от multi_query()
! В большинстве случаев есть лучший способ выполнения файлов SQL. Если у вас есть отдельные запросы, не объединяйте их вместе, а выполняйте каждый по отдельности.
Если вам действительно нужно использовать multi_query()
, и вы хотите заключить его в транзакцию, вы должны поставить commit()
после петли блокировки. Все результаты необходимо повторить, прежде чем вы сможете выполнитьCOMMIT;
команда.
$mysqli->begin_transaction();
$sql1 = "insert into test (Name) values ('pratik5');";
$sql1 .= "insert into test (Name) values ('pratik6');";
$test = $mysqli->multi_query($sql1);
// $mysqli->commit();
do {
$result = $mysqli->use_result();
if ($result) {
// process the results here
$result->free();
}
} while ($mysqli->next_result());
$mysqli->store_result(); // To fetch the error as exception
$mysqli->commit();
Конечно, чтобы увидеть любую из ошибок mysqli, вам необходимо включить режим исключений. Просто поставьте эту строку передnew mysqli()
:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);