Mysql

如果 MySQL 儲存過程包含成功的查詢,則不會引發錯誤

  • October 2, 2019

我正在編寫一個簡單的 MySQL 儲存過程,並使用 PDO 從 PHP 呼叫它。

這很簡單,足以說明我遇到的問題。

這個程序test_procedure_2,不會拋出任何錯誤。

CREATE PROCEDURE `test_procedure_1`()
BEGIN    
   select 'gary';

   CALL raise_error;
END

它返回結果集:

[0] => Array
   (
       [0] => Array
           (
               [gary] => gary
           )

   )

然而…

CREATE PROCEDURE `test_procedure_2`()
BEGIN            
   CALL raise_error;

   select 'gary';
END

…這個過程test_procedure_2,當被呼叫時,實際上會拋出一個帶有消息的 PDOException "SQLSTATE[42000]: Syntax error or access violation: 1305 PROCEDURE vjs_admin_dev.raise_error does not exist"。並且select 'gary'查詢永遠不會被執行。我喜歡。

為什麼第一個過程在明顯包含錯誤時沒有拋出錯誤?我想提出一個儲存過程,每當其中任何地方出現錯誤時,它都會拋出錯誤。謝謝你的幫助!

我正在使用

  • Ubuntu:16.04 和
  • PHP: 7.0.15
  • mysql:版本 14.14 Distrib 5.7.21,適用於 Linux (x86_64),使用 EditLine 包裝器

編輯:我已經用 mysqli 和 PDO 嘗試過這個,我得到了相同的結果。以下是一些顯示我的問題的簡單程式碼範例:

Mysqli 版本的輸出是:

Running test_procedure_1
CREATE PROCEDURE `test_procedure_1`()BEGIN  select 'gary';  CALL raise_error;END
RESULTS:Array ( [gary] => gary ) 
----------
Running test_procedure_2
CREATE PROCEDURE `test_procedure_2`()BEGIN  CALL raise_error;   select 'gary';END
CALL failed: (1305) PROCEDURE vjs_admin_dev.raise_error does not exist
----------

PDO 版本的輸出為:

Running test_procedure_1
CREATE PROCEDURE `test_procedure_1`()BEGIN  select 'gary';  CALL raise_error;END
RESULTS:Array ( [gary] => gary [0] => gary ) 
----------
Running test_procedure_2
CREATE PROCEDURE `test_procedure_2`()BEGIN  CALL raise_error;   select 'gary';END
CALL failed: (42000) Array ( [0] => 42000 [1] => 1305 [2] => PROCEDURE vjs_admin_dev.raise_error does not exist ) 
----------

哇,這是一個相關的問題,因為您無法辨識過程中的錯誤,並且 this 通過隱藏在一些有效結果旁邊!我認為你的問題應該更受歡迎。

經過多次嘗試和測試,我找到了解決它的方法。您希望循環遍歷過程結果集,直到沒有更多結果可用(PHP 手動方式)。但是,我發現了一個替代控制流來擷取先前有效結果之後的錯誤:

<?php
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
   $sql="CALL procedureWithErrors()";      
   $conn=new mysqli($host,$user,$pass,$db);

   /*The usual way (does not work)*/
   $conn->multi_query($sql);
   do{
       $result=$conn->store_result();      
       if($result){
           while($row=$result->fetch_assoc()){
               print_r($row);
           }
           $result->free();
       }                           
   }while($conn->more_results() && $conn->next_result());

   /*This way works*/
   $conn->multi_query($sql);
   while(($result=$conn->store_result())!==FALSE){
       if($result){
           while($row=$result->fetch_assoc()){
               print_r($row);
           }
           $result->free();
       }                           
       $conn->next_result();
   }

   /*Just closing and error handling, not relevant code from here*/
   $conn->close();         

}catch(mysqli_sql_exception | Exception $e){    
   $error="Error #".$e->getCode()." ".$e->getMessage().PHP_EOL;    
   if(isset($conn) && get_class($e)=="mysqli_sql_exception")
       $error.="SQLSTATE #".$conn->sqlstate." Statement: $sql".PHP_EOL;
   $error.=$e->getTraceAsString();
   echo(nl2br($error));    
}finally{
   if(isset($result) && $result instanceof mysql_result) $result->free();          
   if(isset($conn) && is_resource($conn) && get_resource_type($conn)==='mysql link') $conn->close();
}
?>

問題似乎就是這樣,mysqli::more_results如果mysql::next_result發生錯誤則返回 FALSE,因此他們無法區分錯誤和缺乏進一步的結果。

那是不可能的。您不能根據後面的語句是否會在儲存過程中引發錯誤來阻止語句的執行。

實現類似的唯一方法是這樣的:

drop procedure if exists test_procedure_3;
DELIMITER $$
CREATE PROCEDURE `test_procedure_3`()
BEGIN
   if exists (select 1 from information_schema.routines where routine_name = 'raise_error' and routine_schema = database()) 
   then
   select 'gary';

   CALL raise_error;
   end if;
END$$
DELIMITER ;

call test_procedure_3;

因此,您只需檢查該程序是否事先存在。沒有其他方法(我嘗試了事務、信號、準備好的語句等等)。

引用自:https://dba.stackexchange.com/questions/203557