如何检测该事务是否已经启动?

时间:2008-11-26 04:59:20

标签: php mysql zend-framework transactions pdo

我正在使用Zend_Db在事务中插入一些数据。我的函数启动一个事务,然后调用另一个也尝试启动事务的方法,当然也失败了(我正在使用MySQL5)。所以,问题是 - 我如何检测到该交易已经开始? 以下是代码示例:

       try {
                    Zend_Registry::get('database')->beginTransaction();

                    $totals = self::calculateTotals($Cart);
                    $PaymentInstrument = new PaymentInstrument;
                    $PaymentInstrument->create();
                    $PaymentInstrument->validate();
                    $PaymentInstrument->save();

                    Zend_Registry::get('database')->commit();
                    return true;

            } catch(Zend_Exception $e) {
                    Bootstrap::$Log->err($e->getMessage());
                    Zend_Registry::get('database')->rollBack();
                    return false;
            }

Inside PaymentInstrument :: create还有另一个beginTransaction语句,它产生的异常表明该事务已经启动。

11 个答案:

答案 0 :(得分:25)

框架无法知道您是否开始了交易。您甚至可以使用框架不知道的$db->query('START TRANSACTION'),因为它不会解析您执行的SQL语句。

重点是,跟踪您是否已启动交易是一项应用程序职责。这不是框架可以做的事情。

我知道有些框架会尝试这样做,并做一些疯狂的事情,例如计算你开始事务的次数,只有当你完成提交或回滚匹配次数时才解决它。但这完全是假的,因为你的任何函数都不知道提交或回滚是否会实际执行,或者它们是否在另一层嵌套中。

(你能告诉我这次讨论了几次吗?: - )

编辑: Propel是一个PHP数据库访问库,它支持“内部事务”的概念,当您告诉它时它不会提交。开始事务只会递增计数器,并且提交/回滚会递减计数器。下面是一个邮件列表线程的摘录,其中我描述了一些失败的场景。


无论喜欢与否,事务都是“全局的”,并且它们不遵循面向对象的封装。

问题情景#1

我致电commit(),我的更改是否已提交?如果我在“内部交易”中运行他们不是。管理外部事务的代码可以选择回滚,我的更改将在我不知情或无法控制的情况下被丢弃。

例如:

  1. 模型A:开始交易
  2. 模型A:执行一些更改
  3. 模型B:开始交易(无声无操作)
  4. 模型B:执行一些更改
  5. 模型B:提交(无声无操作)
  6. 模型A:回滚(丢弃模型A更改和模型B更改)
  7. 模特B:WTF!?我的变化怎么了?
  8. 问题情景#2

    内部事务回滚,它可能会丢弃外部事务所做的合法更改。当控制权返回到外部代码时,它认为其事务仍处于活动状态且可以提交。使用你的补丁,他们可以调用commit(),并且因为transDepth现在为0,所以它会默默地将$transDepth设置为-1,并且在没有提交任何内容后返回true。

    问题情景#3

    如果我在没有活动的情况下拨打commit()rollback(),则会将$transDepth设置为-1。下一个beginTransaction()将级别增加到0,这意味着事务既不能回滚也不能提交。对commit()的后续调用只会将事务减少到-1或更远,并且在你再做一个多余的beginTransaction()再次增加级别之前,你将永远无法提交。

    基本上,尝试在不允许数据库进行簿记的情况下管理应用程序逻辑中的事务是一个注定要失败的想法。如果要求两个模型在一个应用程序请求中使用显式事务控制,则必须打开两个数据库连接,每个模型一个。然后,每个模型都可以拥有自己的活动事务,可以相互独立地提交或回滚。

    (见http://www.nabble.com/Zend-Framework-Db-Table-ORM-td19691776.html

答案 1 :(得分:4)

执行try / catch:如果异常是事务已经启动(基于错误代码或字符串的消息,无论如何),继续。否则,再次抛出异常。

答案 2 :(得分:2)

将beginTransaction()的返回值存储在Zend_Registry中,稍后再检查。

答案 3 :(得分:2)

查看Zend_Db以及适配器(mysqli和PDO版本)我真的没有看到任何检查事务状态的好方法。似乎有一个ZF issue就此而言 - 幸运的是,很快就会推出一个补丁。

目前,如果您不想运行非正式的ZF代码,mysqli documentation表示您可以SELECT @@autocommit查看您目前是否正在进行交易(错误...不是在自动提交模式下)。

答案 4 :(得分:1)

您还可以按照以下方式编写代码:

try {
    Zend_Registry::get('database')->beginTransaction();
} 
catch (Exception $e) { }

try {
    $totals = self::calculateTotals($Cart);

    $PaymentInstrument = new PaymentInstrument;
    $PaymentInstrument->create();
    $PaymentInstrument->validate();
    $PaymentInstrument->save();

    Zend_Registry::get('database')->commit();
    return true;
} 
catch (Zend_Exception $e) {
    Bootstrap::$Log->err($e->getMessage());
    Zend_Registry::get('database')->rollBack();
    return false;
}

答案 5 :(得分:1)

对于innoDB,您应该可以使用

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();

答案 6 :(得分:1)

这个讨论相当陈旧。正如一些人所指出的,你可以在你的应用程序中完成它。 PHP有一个方法,因为版本5> = 5.3.3,知道你是否处于事务中。 PDP :: inTransaction()返回true或false。链接http://php.net/manual/en/pdo.intransaction.php

答案 7 :(得分:0)

在面向Web的PHP中,几乎总是在单个Web请求期间调用脚本。在这种情况下,您真正​​想要做的是启动事务并在脚本结束之前提交它。如果出现任何问题,请抛出异常并回滚整个事情。像这样:

wrapper.php:

try {
   // start transaction
   include("your_script.php");
   // commit transaction
} catch (RollbackException $e) {
   // roll back transaction
}

使用分片时,情况会变得复杂一些,您可能会打开多个连接。您必须将它们添加到应该在脚本结束时提交或回滚事务的连接列表中。但是,请注意,在分片的情况下,除非事务上有全局互斥,否则您将无法轻松实现并发事务的真正隔离或原子性,因为另一个脚本可能在您提交时将其事务提交到分片你的。但是,您可能想要查看MySQL的distributed transactions

答案 8 :(得分:0)

使用zend profiler查看begin作为查询文本,Zend_Db_Prfiler :: TRANSACTION作为查询类型,然后将提交或回滚作为查询文本。 (假设您的应用程序中没有 - >查询(“START TRANSACTION”)和启用了zend profiler)

答案 9 :(得分:0)

我不同意Bill Karwin的评估,即跟踪交易开始是不可思议的,尽管我喜欢这个词。

我遇到的情况是我有一些事件处理函数,可能被我写的模块调用。我的事件处理程序在db中创建了大量记录。如果某些东西没有正确通过或丢失或者有些事情发生,那么我肯定需要回滚,好吧,cockamamie。我无法知道触发事件处理程序的外部模块代码是否正在处理数据库事务,因为代码是由其他人编写的。我还没有找到一种方法来查询数据库以查看事务是否正在进行中。

所以我要继续数。我正在使用CodeIgniter,如果我要求它开始使用嵌套的数据库事务(例如多次调用它的trans_start()方法),它似乎做了一些奇怪的事情。换句话说,我不能在我的事件处理程序中包含trans_start(),因为如果外部函数也使用trans_start(),则回滚和提交不会正确发生。总有可能我还没有想出正确管理这些功能,但我已经进行了很多测试。

我的所有事件处理程序都需要知道,是否已经由另一个调用的模块启动了db事务?如果是这样,它不会启动另一个新事务,也不会遵守任何回滚或提交。它确实相信如果某个外部函数启动了一个db事务,那么它也将处理回滚/提交。

我有CodeIgniter事务方法的包装函数,这些函数递增/递减计数器。

function transBegin(){
    //increment our number of levels
    $this->_transBegin += 1;
    //if we are only one level deep, we can create transaction
    if($this->_transBegin ==1) {
        $this->db->trans_begin();
    }
}

function transCommit(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can commit transaction
        $this->db->trans_commit();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;

}

function transRollback(){
    if($this->_transBegin == 1) {
        //if we are only one level deep, we can roll back transaction
        $this->db->trans_rollback();
    }
    //decrement our number of levels
    $this->_transBegin -= 1;
}

在我的情况下,这是检查现有数据库事务的唯一方法。它有效。我不会说"应用程序正在管理数据库事务"。在这种情况下,这真的是不真实的。它只是检查应用程序的其他部分是否已启动任何数据库事务,以便它可以避免创建嵌套数据库事务。

答案 10 :(得分:0)

也许您可以尝试PDO :: inTransaction ...如果事务当前处于活动状态,则返回TRUE,否则返回FALSE。 我没有测试过自己,但看起来还不错!