У меня есть пример кода:
<?php
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=test_sql',
'root',
'',
array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
$pdo->query('DROP FUNCTION IF EXISTS tst');
$pdo->query('DROP PROCEDURE IF EXISTS tst2');
$pdo->query('CREATE FUNCTION tst() RETURNS VARCHAR(5) BEGIN RETURN \'x123456\'; END');
$pdo->query('CREATE PROCEDURE tst2() BEGIN SELECT tst(); END');
$st = $pdo->prepare('CALL tst2()');
try {
$st->execute();
} catch (Throwable $ex) {
var_dump($ex->getMessage());
$st->closeCursor(); // same with unset($st)
var_dump('This is never executed');
}
Итак, это PROCEDURE (tst2), который возвращает единственный набор результатов, в котором я называю FUNCTION. Но этот FUNCTION (каким-то образом) сломан, поскольку он возвращает данные слишком долго для своего предложения RETURNS.
Когда я запускаю этот файл, я получаю ожидаемый результат:
string(93) "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'tst()' at row 1"
Но моя проблема возникает позже: PDOStatement кажется "сломанным", и когда PHP пытается собрать его мусор, или когда я пытаюсь выполнить closeCursor() (чтобы я мог снова выполнить другие запросы), или когда я пытаюсь его unset (для демонстрации) , то PHP застрял. Он никогда не достигает другого var_dump, он просто ... простаивает навсегда ?!
У меня есть эта проблема на PHP 5.6.35, но также на 7.1.16 и 7.2.4 (все с использованием драйвера mysqlnd 5.0.5 или 5.0.12). Я использую MySQL 5.7.21 (на других версиях MySQL не пробовал).
Есть подсказка?
Между тем, я также пробовал без использования подготовленных операторов (то есть с использованием $pdo->query) и даже с использованием функций mysqli: та же проблема, PHP завис.
Но, изменив содержимое процедуры, чтобы она не пыталась вернуть набор результатов, в котором происходит SIGNAL, PHP больше не зависает. Так что это будет "работать" (работа = я получаю исключение SQL в моем коде, и мой скрипт PHP не заморожен)
<?php
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=test_sql',
'root',
'',
array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
$pdo->query('DROP FUNCTION IF EXISTS tst');
$pdo->query('DROP PROCEDURE IF EXISTS tst2');
$pdo->query('CREATE FUNCTION tst() RETURNS VARCHAR(5) BEGIN RETURN \'x123456\'; END');
$pdo->query('CREATE PROCEDURE tst2() BEGIN SET @x := (SELECT tst()); END');
$st = $pdo->prepare('CALL tst2()');
try {
$st->execute();
} catch (Throwable $ex) {
var_dump($ex->getMessage());
$st->closeCursor(); // same with unset($st)
}
var_dump('end');
Фактически, кажется, что, поскольку ошибка SQL возникает внутри набора результатов, PDO получает заголовки набора результатов из MySQL, затем получает исключение и поэтому останавливается на этом и никогда не «закрывает» набор результатов (оператор будет оставлен «открытым», в ожидании данных, которые никогда не поступят).
Увидев все это, я сообщил об ошибке команде PHP, потому что я получил то же самое на mysqli и PDO, с любым параметром PDO и в очень конкретном случае.
@RyanVincent Я даже не могу этого сделать, так как, когда мусор PHP собирает инструкцию $st, он также застревает. Если вы попробуете этот код, ваш PHP-процесс будет выглядеть замороженным (даже вне Apache, в PHP как CLI). То же самое, если вы закомментируете closeCursor.
Здесь нет исключения, только string(22) "This is never executed", если он перемещен за пределы блока catch.
PHP 7.0.30-0+deb9u1 (cli) (built: Jun 14 2018 13:50:25) ( NTS )mysql Ver 15.1 Distrib 10.1.26-MariaDB , for debian-linux-gnu (x86_64) using readline 5.2Это ненормально, если у вас вообще нет исключения, потому что PDO должен повторно выдать ошибку SQL. Возможно, он ведет себя по-другому на Maria, чем на Mysql (я также пробовал на сервере OVH, и на Linux тоже, на PHP застрял на closeCursor, пока вы не ЗАКОНЧИТЕ соединение с другим клиентом MySQL)
То же самое с mysql Ver 14.14 Distrib 5.5.43, for debian-linux-gnu (x86_64) using readline 6.2 и PHP 5.4.39-0+deb7u2 (cli) (built: Mar 25 2015 08:33:29). Работает без проблем.
Тогда это может быть проблема с драйвером mysqlnd (поскольку она также возникает в mysqli, и этот mysqlnd использовался во всех моих проверках среды). Тем не менее, вы всегда должны вызывать исключение. Вы использовали MariaDB во втором тесте?






Когда возникает исключение базы данных, не пытайтесь быть умным и пытайтесь помочь базе данных очиститься. Механизм базы данных разберется в этом беспорядке. Все, что вы можете сделать, это выйти из этой операции. Я бы весь этот код завернул в транзакцию. В "улове" просто откат. В противном случае совершите. Возможно, удастся продолжить использование соединения. Это действительно зависит от того, в чем была ошибка базы данных.