Как вызвать две хранимые процедуры MySQL в одном и том же соединении mysqli, используя подготовленные операторы (или другой метод запроса, одинаково безопасный для SQL-инъекций) без получения следующих ошибок:
Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /... Warning: mysqli::prepare(): MySQL server has gone away in /...
Получил код, подключенный к сети через tutorialspoint
Я делаю PHP-сервер с базой данных MySQL. У меня есть два результата, которые я хочу получить из одного запроса: список недельных сводок и резюме всех недель.
┌───────┬────────────┬────────────┬───── | Week | Sales | Commission | ... ├───────┼────────────┼────────────┼───── | week1 | $7,912.12 | $923.41 | ... | week2 | $6,423.48 | $824.87 | ... | week3 | $8,180.67 | $634.04 | ... | ... | ... | ... | ... ├───────┼────────────┼────────────┼───── | total | $67,012.23 | $7,532.58 | ... | avg | $7,012.54 | $787.38 | ... └───────┴────────────┴────────────┴─────
Я просто хранили еженедельные сводки в таблице базы данных и использовал хранимую процедуру для получения сводки всех недельных сводок. В моем PHP-коде я просто выбрал все строки в таблице week
а затем вызвал хранимую процедуру getWeeksSummary
.
Теперь я должен иметь возможность фильтровать данные в еженедельных сводках. Я заменил простой SELECT ... FROM week
хранимой процедурой getWeeks()
чтобы вычислить все еженедельные сводки.
$weeksSummary = new stdClass(); if ($stmt = $mysqli->prepare('CALL getWeeks(?,?,?);')) { $stmt->bind_param('sss', $a, $b, $c); $stmt->execute(); $stmt->bind_result($week, $sales, $commission, ...); $weeksSummary->weeks = []; while($stmt->fetch()) { $week = new stdClass(); $week->week = $week; $week->sales = $sales; $week->commission = $commission; ... $weeksSummary->weeks[] = $week; } $stmt->free_result(); $stmt->close(); } if ($stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?);')) { $stmt->bind_param('sss', $a, $b, $c); $stmt->execute(); $stmt->bind_result($avgSales, $totSales, $avgCommission, $totCommission ...); $stmt->fetch(); $weeksSummary->summary = new stdClass(); $weeksSummary->summary->avgSales = $avgSales; $weeksSummary->summary->totSales = $totSales; $weeksSummary->summary->avgCommission = $avgCommission; $weeksSummary->summary->totCommission = $totCommission; ... $stmt->free_result(); $stmt->close(); } echo json_encode($weeksSummary);
в$weeksSummary = new stdClass(); if ($stmt = $mysqli->prepare('CALL getWeeks(?,?,?);')) { $stmt->bind_param('sss', $a, $b, $c); $stmt->execute(); $stmt->bind_result($week, $sales, $commission, ...); $weeksSummary->weeks = []; while($stmt->fetch()) { $week = new stdClass(); $week->week = $week; $week->sales = $sales; $week->commission = $commission; ... $weeksSummary->weeks[] = $week; } $stmt->free_result(); $stmt->close(); } if ($stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?);')) { $stmt->bind_param('sss', $a, $b, $c); $stmt->execute(); $stmt->bind_result($avgSales, $totSales, $avgCommission, $totCommission ...); $stmt->fetch(); $weeksSummary->summary = new stdClass(); $weeksSummary->summary->avgSales = $avgSales; $weeksSummary->summary->totSales = $totSales; $weeksSummary->summary->avgCommission = $avgCommission; $weeksSummary->summary->totCommission = $totCommission; ... $stmt->free_result(); $stmt->close(); } echo json_encode($weeksSummary);
Этот код работал нормально, когда первый подготовленный оператор был SELECT week, sales, commission, ... FROM week WHERE a=?, b=?, c=?;
вместо CALL getWeeks(?,?,?);
, Теперь я получаю эти ошибки:
Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /... Warning: mysqli::prepare(): MySQL server has gone away in /...
1) Ошибка: я использовал новый объект $stmt2
для второго запроса. Те же ошибки.
2) Успех: я закрыл соединение mysqli
и открыл новый до второго оператора. Второе соединение mysqli
с его собственным подготовленным оператором работает нормально, но код для подключения к базе данных остается полностью отдельным, так что это действительно не помогает.
3) Не удалось: просто из любопытства я вернулся к исходному рабочему коду и переупорядочил инструкции, поставив инструкцию хранимой процедуры перед SELECT
. Те же ошибки. Таким образом, соединение mysqli
отлично с запросами перед хранимой процедурой, но ничего не нравится после хранимой процедуры.
4) Ошибка: я попытался поставить $mysqli->next_result();
после первого заявления. Те же ошибки. Однако, если я использую query()
вместо prepare()
для вызова хранимых процедур, next_result()
действительно разрешает запуск обеих хранимых процедур. Я бы хотел использовать подготовленный оператор, поскольку они помогают против инъекций SQL.
A): я мог бы разделить его на два вызова фонового содержимого, но при обновлении данных резюме не будут синхронизироваться с интерфейсом.
B): я мог бы присоединиться к ним в одну хранимую процедуру MySQL, а затем разделить их на PHP, но мне тоже нужны они отдельно, поэтому один и тот же код будет там дважды.
C): Я мог бы прекратить использование подготовленных операторов, но я не знаю других способов избежать инъекций SQL.
Какие-либо предложения?
Ну, я постараюсь ответить за заголовок вопроса , считая, что в первом утверждении не был вызван обычный запрос, но одна из двух вышеупомянутых хранимых процедур .
После вызова хранимой процедуры вам всегда нужно переместить дополнительный пустой набор результатов, возвращаемый каждой хранимой процедурой:
$mysqli->next_result();
Кроме того, после первого вызова функции, добавьте один дополнительный fetch () после получения ваших данных:
$stmt->fetch(); $stmt->free_result();
так как вам нужно «освободить» набор результатов, ожидающий на стороне сервера. Это можно сделать разными способами, но проще всего просто вызвать fetch () еще раз, или, более строго говоря, вам нужно вызвать fetch (), пока не вернет FALSE, указывая, что в наборе результатов больше нет строк , Вы делаете это [молча] в других фрагментах, когда вызываете fetch()
в цикле while, но здесь, выбирая только одну строку, вы должны явно ее вызывать.
Есть и другой способ, путь более удобный: используйте get_result()
(если доступно), который решит все ваши проблемы сразу. Вместо этого длинного и ветренного кода, который у вас есть на данный момент, нужны только четыре строки:
$stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?)'); $stmt->bind_param('sss', $a, $b, $c); $stmt->execute(); $weeksSummary = $stmt->get_result()->fetch_object();
get_result()
освободит этот ожидающий набор результатов и в то же время позволит вам использовать fetch_object()
, который позволит вам получить результирующий объект только в одной строке.
Читая документацию mysqli
, говорится, что $stmt->free_result()
– освободить память, выделенную из $stmt->store_result()
. Поскольку код не использует store_result()
, удаление free_result()
решает ошибки.
Конечно, он также говорит, что следует использовать store_result()
всякий раз, когда запрос возвращает набор результатов. Я действительно не понимаю, почему (что-то делать с буферизацией), но поскольку эти два подготовленных оператора и хранимые процедуры работают без store_result()
, проблема решена.
Мне все еще интересно, почему он не работает с store_result()
и free_result()
, но, по крайней мере, сейчас есть какой-то рабочий код. Вот модифицированный код в tutorialspoint.
В качестве побочного примечания вместо использования двух подготовленных операторов с двумя хранимыми процедурами путь вокруг него заключается в использовании одного подготовленного оператора для установки переменных
$stmt = $mysqli->prepare('SET @a = ?, @b = ?, @c = ?') ...
затем используйте эти переменные в запросах для вызова хранимых процедур
$result = $mysqli->query('CALL getWeeks(@a,@b,@c)') ... $result = $mysqli->query('CALL getWeeksSummary(@a,@b,@c)') ...