30

我正在使用 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 语句,它产生表示事务已经开始的异常。

4

11 回答 11

29

框架无法知道您是否启动了事务。您甚至可以使用$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()的操作以再次增加级别。

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

于 2008-11-26T06:53:03.043 回答
4

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

于 2008-11-26T06:24:57.717 回答
2

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

于 2008-11-26T05:25:52.127 回答
2

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

目前,如果您不想运行非官方的 ZF 代码,mysqli 文档说您可以确定SELECT @@autocommit您当前是否处于事务中(错误...不是处于自动提交模式)。

于 2008-11-26T05:33:31.970 回答
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;
}
于 2015-03-14T13:09:20.210 回答
1

对于 innoDB,您应该能够使用

SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
于 2015-07-05T13:47:54.683 回答
1

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

于 2017-06-08T10:07:20.320 回答
0

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

于 2014-05-15T07:28:54.090 回答
0

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

wrapper.php:

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

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

于 2013-07-15T01:50:05.717 回答
0

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

于 2018-03-14T14:20:00.387 回答
0

我不同意 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;
}

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

于 2017-04-01T04:33:51.703 回答