PHP mysqli НЕ перехватывает некоторые ошибки при вызове хранимой процедуры

Мы создаем производственное приложение 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.

Я не уверен, но мне кажется, что вам не хватает mysqli_stmt_affected_rows() > 0. Кроме того, вы можете получить параметр OUT.

Ken Y-N 12.02.2019 07:12

Спасибо @KenY-N. Впоследствии я извлекаю параметр OUT, и он становится нулевым (что ожидается всякий раз, когда происходит сбой хранимого процесса). Я просто не указал, что в фрагменте кода выше bcoz это было несколько неактуально. Что касается stmt_affected_rows, я посмотрел. Это только для запросов SELECT, INSERT и т. д. Не для хранимых процессов. И в любом случае не кажется правильным способом ловушки ошибок.

vharihar 12.02.2019 07:18

Любое предложение? Это становится действительно надоедливым, если я не могу надежно перехватывать ошибки базы данных в PHP.

vharihar 12.02.2019 08:20

Возможный дубликат Как получить информацию об ошибках MySQLi в разных средах — я думаю, что строка mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); нужна для включения сквозной передачи всех исключений.

Ken Y-N 12.02.2019 09:35

Нет, ни один из них не имел никакого значения.

vharihar 12.02.2019 10:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
5
196
2

Ответы 2

Ааа, поломав над этим голову, долго и много гуглил, нашел проблему! Он тесно связан с Как вызвать хранимую процедуру внутри другой хранимой процедуры (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());

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