MySql PDO 和二级内部过程调用中抛出的异常

Posted

技术标签:

【中文标题】MySql PDO 和二级内部过程调用中抛出的异常【英文标题】:MySql PDO and exceptions thrown within 2nd level internal procedure calls 【发布时间】:2016-07-26 02:11:45 【问题描述】:

我们有以下情况...

DROP PROCEDURE IF EXISTS unit_throw_error;
CREATE PROCEDURE unit_throw_error() CALL throw_error();

DROP PROCEDURE IF EXISTS throw_error;
CREATE PROCEDURE throw_error() CALL _proc_does_not_exist();

如果调用第一个过程unit_throw_errorphp PDO 对象将不会抛出通过第二个过程创建的异常。一些示例代码:

$dsn = "mysql:host=localhost;dbname=test";
$username = "...";
$password = "...";

$pdo = new PDO($dsn, $username, $password, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);

$query = "CALL unit_throw_error();";
$stmt = $pdo->prepare($query);
$stmt->execute();

do 
    $results = $stmt->fetchAll(PDO::FETCH_ASSOC);
 while ($stmt->nextRowset() && $stmt->columnCount());

$stmt->closeCursor();

预期的结果是抛出PDOException,但这不会发生。有什么想法吗?

编辑

我们已将这种情况隔离为仅在成功选择发生在引发异常的行之前发生...

-- CREATE DATABASE `exception_test` /*!40100 DEFAULT CHARACTER SET utf8 */;

use exception_test;

DROP TABLE  if exists `test_data`;
CREATE TABLE `test_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `some_field` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

INSERT INTO `test_data` (`some_field`) VALUES ('sdf');
INSERT INTO `test_data` (`some_field`) VALUES ('fgh');
INSERT INTO `test_data` (`some_field`) VALUES ('ghj');

drop procedure if exists `unit_throw_error`;
create procedure unit_throw_error() call unit_throw_error_2();

drop procedure if exists `unit_throw_error_2`;
delimiter //
create procedure unit_throw_error_2()
  begin

  select * from test_data;

  call unknown_procedure();

  end//
delimiter ;

有谁知道为什么选择查询导致异常不被抛出?

【问题讨论】:

我得到了两个例外:Syntax error or access violation: 1305 PROCEDURE test._proc_does_not_exist does not exist 另外值得一提的是,对于单行程序,您可以跳过 BEGINEND 以及分隔符更改。制作更简单的 MCVE。 感谢 @miken32 的 cmets。我进一步隔离了这一点并更新了问题。 【参考方案1】:

在处理存储过程时,you need to call next_result()方法,为了让PDO知道该过程返回的其他结果。

所以,代码应该是

$stmt = $pdo->prepare("CALL unit_throw_error()");
$stmt->execute();
do 
    $data = $stmt->fetchAll();
    var_dump($data);
 while ($stmt->nextRowset() && $stmt->columnCount());

在这种情况下,PDO 会从数据库中获取下一个结果,如果这个结果是错误的,则会抛出异常。

编辑:

对于这种 syntax 错误的特殊情况(即甚至在执行之前发生的错误),您实际上不需要调用nextRowset()。相反,只需确保将 PDO 设置为 ERRMODE_EXCEPTION

但是,无论如何,nextRowset() 必须使用存储过程调用,以便让其他查询运行,并在需要多个结果集时得到错误。

所以(只是为了证明我的回答有用),如果您将代码更改为:

drop procedure if exists `unit_throw_error`;
create procedure unit_throw_error() call unit_throw_error_2();

drop procedure if exists `unit_throw_error_2`;
delimiter //
create procedure unit_throw_error_2()
  begin

  select * from test_data;
  select * from non_existent;

  end//
delimiter ;

在没有nextRowset()调用的情况下运行PDO代码,不会出现错误。但是我在上面发布的代码会抛出错误,

SQLSTATE[42S02]:未找到基表或视图:1146 表 'exception_test.non_existent' 不存在

【讨论】:

不幸的是,我们已经尝试过了,但仍然没有出现错误。我已经用我们正在使用的示例代码更新了这个问题。 我可以看到你在 PDO 方面有相当多的经验,这很有趣,因为我在研究这个问题时发现了你的文章。顺便说一句,好文章! 我已经重新检查过了,你的回答是 100%。当我们尝试了这一切时,我有点震惊。我看到虽然在我们研究的某些时候我们已经将 ERRMODE_EXCEPTION 设置为 ERRMODE_SILENT....但是,我们现在重新测试了这个,并且 $pdo->errorCode() 不报告错误,其中 ERRMODE_EXCEPTION 将通过异常。 嗯。有了你的代码,我得到了Syntax error or access violation: 1305 PROCEDURE exception_test.unknown_procedure does not exist'。什么 PHP 版本? PHP 5.6,但我们现在发现即使调用 $pdo->errorCode(),错误也仅显示 ERRMODE_EXCEPTION 而不是 ERRMODE_SILENT。

以上是关于MySql PDO 和二级内部过程调用中抛出的异常的主要内容,如果未能解决你的问题,请参考以下文章

NSOrderedSet 生成的访问器中抛出的异常

NSOrderedSet 生成的访问器中抛出的异常

如何捕获 node_modules 中抛出的异常

捕获在不同线程中抛出的异常

springboot如何处理过滤器filter中抛出的异常

捕获 AuthenticationProvider 中抛出的异常