【问题标题】:When will automatic rollbacks be executed in PHP executing Oracle PL/SQL - OCI8?何时将在执行 Oracle PL/SQL - OCI8 的 PHP 中执行自动回滚?
【发布时间】:2010-06-22 15:59:10
【问题描述】:

我有执行存储过程 10 次的 PHP 代码。如果一个存储过程调用失败,它应该继续,并在最后提交事务。

基本上是这样的:

$connection = getConn();

foreach($row as $i=>$j) {
  $SQL = "BEGIN MYPROC.EXECUTE(:VAL1, :VAL2); END;";
  $statement = OCIParse($connection, $SQL);

  oci_bind_by_name($statement, 'VAL1', $row[i]['FIRSTVAL']);
  oci_bind_by_name($statement, 'VAL2', $row[i]['SECONDVAL']);

  $success = @OCIExecute($statement, OCI_DEFAULT);
  if(!$success) {
    print 'Exception in stored proc call';
  }
  else {
    print 'Success';
  }

}
oci_commit($connection);

我的问题是,如果在第 5 次存储过程调用中出现异常,是否会回滚到该点为止的所有存储过程调用?

【问题讨论】:

  • 好问题。当第 5 次存储过程调用失败时,您现在会出现什么行为?
  • 它没有回滚任何东西。成功的执行都在提交中。这就是我感到困惑的原因,因为在本网站的 1.10.4 (soft.buaa.edu.cn/oracle/bookshelf/Oreilly/langpkt/ch01_10.htm) 中,它表示当控制权返回给调用应用程序时,未处理的异常将被回滚。
  • 这个页面的说法不同:stanford.edu/dept/itss/docs/oracle/10g/appdev.101/b10807/… 在“捕获未处理的异常”部分它指出,“未处理的异常也会影响子程序。如果您成功退出子程序,PL/SQL 会将值分配给 OUT 参数. 但是,如果您以未处理的异常退出,PL/SQL 不会为 OUT 参数赋值(除非它们是 NOCOPY 参数)。此外,如果存储的子程序因未处理的异常而失败,PL/SQL 不会回滚数据库工作由子程序完成。”
  • 我不确定如何处理 PHP 和内联 PLSQL,以及它们是否被视为上述评论中性质的“子程序”,或者它们是否被视为顶级程序,在这种情况下我(假设)它会回滚?
  • Oracle 不会“为您”回滚任何内容,无论是否存在未处理的异常。自己测试一下:对表执行更新。执行第二次更新,其中某个值 = 1/0,引发异常。现在重新选择原始更新中的行 - 它仍然更改。您有责任提交和回滚您的更改。

标签: php oracle plsql transactions


【解决方案1】:

只要每个过程都在同一个会话中执行,并且它们都没有发出提交,那么它们所做的更改就可以回滚。您应该在循环外打开连接,然后在其中完成所有工作。就目前而言,您每次都通过循环连接,这是低效的,并且不允许您做您想做的事情。您还应该将 commit 语句放在循环之外。

可能是这样的:

$SQL = "BEGIN MYPROC.EXECUTE(:VAL1, :VAL2); END;";
$connection = getConn();
$statement = OCIParse($connection, $SQL);

foreach($row as $i=>$j) {

  oci_bind_by_name($statement, 'VAL1', $row[i]['FIRSTVAL']);
  oci_bind_by_name($statement, 'VAL2', $row[i]['SECONDVAL']);

  $success = @OCIExecute($statement, OCI_DEFAULT);
  if(!$success) {
    print 'Exception in stored proc call';
    oci_rollback($connection);
    exit processing here... 
  }
  else {
    print 'Success';
  }
}
oci_commit($connection);

【讨论】:

  • 抱歉没有更明确...... getConn() 函数返回一个单例连接,所以它都是相同的连接。你的答案是我所期望的,但由于某种原因,所有成功的执行都被提交了,即使 10 次执行中的第 5 次因未处理的异常而失败。
  • 我稍微编辑了代码,最终结果相同,更清晰。
  • 当第 5 次失败时,您是否正在回滚您的事务?您在原始代码中的提交在您的循环内,这意味着每次通过循环您的更改都是永久的。需要把commit放到循环外,遇到异常就不要做。
  • 另外,您不需要每次循环都重新解析语句。只需重新绑定参数值就足够了。
  • 您的示例不会在出现错误的情况下中止处理。仅仅因为第 5 个过程生成 Oracle 异常并不意味着所有先前的更新都会回滚。如果您在异常发生后继续处理并执行提交,则成功的更新仍然有效,您的提交会将它们保存以供后代使用。
【解决方案2】:

我认为 PHP 驱动程序而不是 Oracle 正在控制这里的提交。 This 似乎表明从 PHP 5.3.2 (PECL OCI8 1.4) 开始,OCIExecute 的每次调用(默认情况下)都会提交语句,而不管存储过程中的内容。

【讨论】:

  • 我会讨厌那个“功能”,除非有一个等效的调用没有提交。我猜 OP 将不得不在数据库中创建另一个执行循环处理的过程。
  • 我使用 OCI_DEFAULT 选项来防止自动提交(现在在较新版本的 PHP 中称为 OCI_NO_AUTO_COMMIT)。
  • @DCookie -是的,它违背了传统思维 - 在对发生类似事情的 JDBC 代码进行故障排除时首次遇到这个概念。
  • @Renderln - 听起来你正在对驱动程序做正确的事情以避免提交 - 我看到你的其他 cmets 之一关于在应用程序代码中吞下异常并且只能猜测有实际代码中的逻辑问题。
  • @dpbradley 那么我们是否都同意即使 SQL 异常冒泡到 PHP 应用程序代码,Oracle 也不会回滚事务?鉴于我已关闭自动提交,此行为将由 DCookie 上面的回答来解释。
【解决方案3】:

最近不得不对此进行一些测试。当发生未处理的异常时,Oracle 似乎会部分回滚到同一会话的最顶层包含开始块或提交的点(并不总是一直回到先前的提交)。给定一个带有 int id 和 varchar2 val 和 proc 的表:

CREATE OR REPLACE PROCEDURE PROC_AUTO_COMMIT_TEST( 
   p_id int, p_val varchar2, p_cmd varchar2
) IS
BEGIN
   if (p_cmd = 'init') then
        delete from TEMP_AUTOCOMMIT_TEST;
        insert into TEMP_AUTOCOMMIT_TEST values(1,'one');
        insert into TEMP_AUTOCOMMIT_TEST values(2,'two');
        insert into TEMP_AUTOCOMMIT_TEST values(3,'three');
        commit;  
   else   
        update TEMP_AUTOCOMMIT_TEST 
           set val = p_val
         where id = p_id;

         if (p_cmd = 'throw') then
            insert into TEMP_AUTOCOMMIT_TEST values(3,'THREE');  -- throws
         end if;
   end if;     
END PROC_AUTO_COMMIT_TEST;

然后执行这个:

begin
    PROC_AUTO_COMMIT_TEST(0, null, 'init');   
    begin
        PROC_AUTO_COMMIT_TEST(1, 'ONE', null);
    end;
    begin
        PROC_AUTO_COMMIT_TEST(2, 'TWO', null);
        PROC_AUTO_COMMIT_TEST(3, 'THREE', 'throw');
    end;
end;

一直回滚到“init”中的提交(ONE 也回滚)。

相对于按顺序执行这些(从 Toad(自动提交关闭,每个块上按 F9,整个事情按 f5)或 Sqlplus 与 / 之间):

begin
    PROC_AUTO_COMMIT_TEST(0, null, 'init');   
end;

begin
    PROC_AUTO_COMMIT_TEST(1, 'ONE', null);
end;

begin
    PROC_AUTO_COMMIT_TEST(2, 'TWO', null);
    PROC_AUTO_COMMIT_TEST(3, 'THREE', 'throw');
end;

在 THREE 中发生的异常随后会回滚到 'ONE' 之后。但是,“ONE”仍然需要回滚或提交,因为它持有行锁(使用 TOAD 中的会话浏览器验证)。将此称为部分回滚,因为它不会一直返回到“init”调用中的提交,并且会锁定一行。我假设这种情况更接近 PHP 可能正在做的事情和其他连接器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-05
    • 1970-01-01
    • 2013-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-02
    相关资源
    最近更新 更多