【问题标题】:How do detect that transaction has already been started?如何检测该事务已经开始?
【发布时间】:2010-09-24 02:28:47
【问题描述】:

我正在使用 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;
            }

在 PaymentInstrument::create 内部还有另一个 beginTransaction 语句,该语句会产生表示事务已经开始的异常。

【问题讨论】:

    标签: php mysql zend-framework transactions pdo


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      查看 Zend_Db 以及适配器(mysqli 和 PDO 版本),我真的没有看到任何检查事务状态的好方法。似乎有一个 ZF issue 与此相关 - 幸运的是,即将发布一个补丁。

      目前,如果您不想运行非官方的 ZF 代码,mysqli documentation 表示您可以通过SELECT @@autocommit 了解您当前是否处于事务中(错误...不是处于自动提交模式) .

      【讨论】:

      • 似乎这个问题在他们的跟踪器中丢失了...... :(
      • 所有 ZF 问题都说“在下一个次要版本中修复”,直到它们真正得到修复。我希望他们这样做是有充分的理由的,因为这非常具有误导性,并且会让很多人感到困惑。
      • 在我通过 mysql 客户端 SELECT @@autocommit; 进行的测试中,交易期间仍然返回 1。
      【解决方案3】:

      尝试/捕获:如果异常是事务已经开始(基于错误代码或字符串的消息,等等),继续。否则,再次抛出异常。

      【讨论】:

        【解决方案4】:

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

        关键是跟踪您是否已开始交易是应用程序的责任。这不是框架可以做的事情。

        我知道一些框架会尝试这样做,并且会做 cockamamie 的事情,比如计算您开始事务的次数,只有在您完成提交或回滚匹配的次数时才会解决它。但这完全是假的,因为您的任何函数都不知道提交或回滚是否真的会这样做,或者它们是否位于另一层嵌套中。

        (你能说我已经讨论过几次了吗?:-)

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

        更新 2:Doctrine DBAL 也有这个功能。他们称之为事务嵌套。


        不管你喜不喜欢,事务都是“全局的”,它们不遵循面向对象的封装。

        问题场景 #1

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

        例如:

        1. 模型 A:开始交易
        2. 模型 A:执行一些更改
        3. 模型 B:开始事务(静默无操作)
        4. 模型 B:执行一些更改
        5. 模型 B:提交(静默无操作)
        6. 模型 A:回滚(放弃模型 A 更改和模型 B 更改)
        7. B 型:WTF!?我的更改发生了什么变化?

        问题场景 #2

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

        问题场景#3

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

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

        【讨论】:

        • 确实如此,尽管它肯定是一个非常好的功能。我想知道 Hibernate 或任何类似的更成熟的持久层中是否存在类似的东西......
        • Propel 有这个,但我仍然认为这是一个虚假的设计。请参阅上面的编辑。
        • 回到这个话题...例如 JPA 有 TransactionManager 的概念,对于 ZF 来说类似的概念是否合乎逻辑?
        • 好吧,我不能和 JPA 说话,但 Java 不是 PHP,反之亦然。 PHP 假设 request 建立了一个工作单元,因此 PHP 应用程序中的事务范围应该与请求的范围相同。这很简单。没有 PHP 应用程序服务器,因此没有事务的“对话”范围。因此,无需让域模型类或 DAO 类管理事务——只需在控制器级别进行即可。
        • @Pacerier,我不知道“bdbaft”是什么意思。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

            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;
            }
            

            【讨论】:

              【解决方案8】:

              对于innoDB,你应该可以使用

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

              【讨论】:

                【解决方案9】:

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

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

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

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

                我有 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】:

                  这个讨论相当老了。正如一些人指出的那样,您可以在您的应用程序中执行此操作。 PHP 从版本 5 >= 5.3.3 开始有一个方法可以知道您是否处于事务中。 PDP::inTransaction() 返回真或假。链接http://php.net/manual/en/pdo.intransaction.php

                  【讨论】:

                  • 可能是框架的DB层没有封装这个用于检查事务状态的PDO方法。我的答案和你一样,在看到你的答案之前,我差点把我的贴出来。
                  【解决方案11】:

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2013-07-15
                    • 1970-01-01
                    • 2014-01-26
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-12-28
                    • 2011-09-09
                    相关资源
                    最近更新 更多