Мы создаем производственное приложение PHP-MySQL и хотим, чтобы хранимые процедуры MySQL были центральным пуленепробиваемым шлюзом к базе данных. Дублирующиеся ключи, таблица не найдена, экземпляр сервера выходит из строя и т. д. Все и любые ошибки должны быть перехвачены и переданы вызывающему веб-интерфейсу PHP, а транзакция откатывается в хранимой процедуре при таких ошибках.
Я использую PHP mysqli и вызываю хранимую процедуру следующим образом:
$stmt = mysqli_prepare($db, "call my_stored_proc(?, ?, ?, @ptid)");
if ($stmt && mysqli_stmt_bind_param($stmt, "sss", 'p1', 'p2', 'p3') &&
mysqli_stmt_execute($stmt) && mysqli_stmt_close($stmt)) {
echo "All fine!"
} else {
echo mysqli_error($db);
db_disconnect($db);
exit;
}
Хранимая процедура выполняет некоторую базовую проверку и сообщает пользовательскому условию, если проверка не пройдена. И, конечно же, мой PHP-код способен уловить и увидеть эти ошибки проверки, не связанные с базой данных (например, форматирование), возникающие из-за хранимой процедуры. После прохождения проверок, не связанных с базой данных, хранимая процедура переходит к проверке, связанной с базой данных, и, если они проходят, она вставляет строку в таблицу и передает идентификатор в последнем параметре OUT.
Моя проблема в том, что если эта вставка не удалась (скажем, ошибка дубликата ключа bcoz или ошибка таблицы не найдена), мой PHP-код просто нет перехватывает ошибку! Пишет "Все в порядке"!
Это почему? Что мне не хватает?
Я хочу, чтобы мой вызов хранимой процедуры был пуленепробиваемым, и все ошибки, вызванные хранимой процедурой, должны быть перехвачены в PHP.
К вашему сведению: если я вызываю хранимую процедуру из клиента mysql (например, MySQL Workbench или клиента mysql в Linux), об ошибках сообщается правильно.
ПОСЛЕДНИЕ РЕДАКТИРОВАНИЯ: К вашему сведению, код хранимой процедуры просто:
delimiter $$
drop procedure if exists my_stored_proc $$
create procedure my_stored_proc
(
in p_name VARCHAR(31),
in p_notes VARCHAR(510),
in p_created_by VARCHAR(31),
out p_pt_id INT
)
begin
declare custom_exception condition for sqlstate '45000';
declare l_retval boolean;
declare l_right_now datetime default now();
select p_name regexp '^[[:space:]]*$' into l_retval;
if l_retval then
signal custom_exception set message_text = 'NAME cannot be blank.';
end if;
select p_name regexp '[^0-9_]' into l_retval;
if l_retval then
signal custom_exception set message_text = 'Invalid NAME.';
end if;
call validate_user_in_db(p_created_by, true, l_retval);
if not l_retval then
signal custom_exception set message_text = 'Invalid CREATED_BY user.';
end if;
insert into some_table
(
NAME, NOTES,
CREATED_BY, CREATED_ON
) values
(
p_name, p_notes,
p_created_by, l_right_now
);
set p_pt_id = last_insert_id();
end $$
delimiter ;
ДАЖЕ ПОЗДНЕЕ ОБНОВЛЕНИЕ:
Странно то, что если я закомментирую вызов validate_user_in_db в вышеприведенной хранимой процедуре, все будет работать нормально, а ошибки будут правильно перехватываться (например, дублирующийся ключ и т. д.) в PHP.
К вашему сведению: validate_user_in_db делает следующее:
create procedure validate_user_in_db (in p_user VARCHAR(127),
in p_active_only boolean, out p_retval boolean)
begin
set p_retval = false;
if p_active_only then
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
and active = true
limit 1;
else
select sql_calc_found_rows 'x'
from SOME_USERS_TABLE
where username = p_user
limit 1;
end if;
set @l_num_rows = found_rows() ;
if @l_num_rows = 1 then
set p_retval = true;
end if;
end $$
Простите за длинный пост. Но я решил дать полную картину.
Что мне не хватает? Почему мой PHP-код не возвращает ошибки, если включен вызов validate_user_in_db? validate_user_in_db постоянно меняет какое-то состояние? Ключевое слово sql_calc_found_rows все портит?
К вашему сведению: это PHP 7.3 и MySQL 5.6.
Спасибо @KenY-N. Впоследствии я извлекаю параметр OUT, и он становится нулевым (что ожидается всякий раз, когда происходит сбой хранимого процесса). Я просто не указал, что в фрагменте кода выше bcoz это было несколько неактуально. Что касается stmt_affected_rows, я посмотрел. Это только для запросов SELECT, INSERT и т. д. Не для хранимых процессов. И в любом случае не кажется правильным способом ловушки ошибок.
Любое предложение? Это становится действительно надоедливым, если я не могу надежно перехватывать ошибки базы данных в PHP.
Возможный дубликат Как получить информацию об ошибках MySQLi в разных средах — я думаю, что строка mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); нужна для включения сквозной передачи всех исключений.
Нет, ни один из них не имел никакого значения.






Ааа, поломав над этим голову, долго и много гуглил, нашел проблему! Он тесно связан с Как вызвать хранимую процедуру внутри другой хранимой процедуры (PHP и mysqli)
По сути, у меня был случай, когда PHP вызывал SP1, который, в свою очередь, вызывал SP2, и все работало нормально в клиенте mysql, но ломалось при вызове PHP!
Оказывается, проблема в том, что SP2 выбирал набор результатов (т. е. SELECT без предложение INTO).
Я переписал SP2, чтобы обязательно выполнить SELECT INTO, и это решило проблему.
Я думаю, что возможность SELECT результирующего набора без выполнения SELECT INTO - дерьмовая функция в MySQL. Если подумать, в MySQL довольно много дерьмовых вещей (сохраненные функции, распространение исключений вверх по стеку, плохая компиляция и точное определение синтаксических ошибок в хранимых процедурах, плохая концепция границ транзакций и т. д.).
Я думаю, что выбора набора результатов в хранимой подпрограмме следует избегать любой ценой.
PHP сообщает об ошибках из хранимых процедур. Проблема здесь в том, что вызов хранимых процедур через mysqli — непростая задача. По возможности лучше избегать хранимых процедур и mysqli.
Все ошибки могут быть сообщены mysqli, если вы включить отчеты об ошибках mysqli. Просто добавьте эту строку перед открытием нового соединения:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
Примечание: до недавнего времени в mysqli было множество ошибок, которые либо приводили к сбою PHP, либо не сообщали об ошибках должным образом. Держите ваш PHP в актуальном состоянии, чтобы избежать таких проблем.
Ваша основная проблема заключается в том, что ваша хранимая процедура выдает результаты, которые вы не читаете в PHP. Результаты из MySQL извлекаются последовательно, даже если запросы выполняются асинхронно. Когда ошибка возникает после SELECT в вашей хранимой процедуре, PHP не сразу выдает ошибку. Вы должны получить каждый результат, даже если он вам не нужен в PHP.
Вы можете использовать простой цикл do-while для получения всех результатов.
$stmt = $mysqli->prepare('CALL SP1()');
echo $stmt->execute();
do {
$stmt->get_result();
} while ($y = $stmt->next_result());
Я не уверен, но мне кажется, что вам не хватает
mysqli_stmt_affected_rows() > 0. Кроме того, вы можете получить параметрOUT.