php 如何检测该事务已经开始?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/319788/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do detect that transaction has already been started?
提问by xelurg
I am using Zend_Db to insert some data inside a transaction. My function starts a transaction and then calls another method that also attempts to start a transaction and of course fails(I am using MySQL5). So, the question is - how do I detect that transaction has already been started? Here is a sample bit of code:
我正在使用 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 there is another beginTransaction statement that produces the exception that says that transaction has already been started.
在 PaymentInstrument::create 内部,还有另一个 beginTransaction 语句会产生异常,表明交易已经开始。
回答by Bill Karwin
The framework has no way of knowing if you started a transaction. You can even use $db->query('START TRANSACTION')which the framework would not know about because it doesn't parse SQL statements you execute.
框架无法知道您是否开始了事务。您甚至可以使用$db->query('START TRANSACTION')框架不知道的内容,因为它不会解析您执行的 SQL 语句。
The point is that it's an application responsibility to track whether you've started a transaction or not. It's not something the framework can do.
关键是跟踪您是否已开始事务是应用程序的责任。这不是框架可以做的。
I know some frameworks try to do it, and do cockamamie things like count how many times you've begun a transaction, only resolving it when you've done commit or rollback a matching number of times. But this is totally bogus because none of your functions can know if commit or rollback will actually do it, or if they're in another layer of nesting.
我知道有些框架会尝试这样做,并且会做一些愚蠢的事情,例如计算您开始事务的次数,只有在您完成提交或回滚匹配的次数后才能解决它。但这完全是假的,因为您的任何函数都不知道提交或回滚是否会实际执行,或者它们是否位于另一层嵌套中。
(Can you tell I've had this discussion a few times? :-)
(你能说我已经讨论过几次了吗?:-)
Update 1:Propelis a PHP database access library that supports the concept of the "inner transaction" that doesn't commit when you tell it to. Beginning a transaction only increments a counter, and commit/rollback decrements the counter. Below is an excerpt from a mailing list thread where I describe a few scenarios where it fails.
更新 1:Propel是一个 PHP 数据库访问库,它支持“内部事务”的概念,当您告诉它时不会提交。开始事务只会增加计数器,提交/回滚会减少计数器。下面是一个邮件列表线程的摘录,我在其中描述了一些失败的场景。
Update 2:Doctrine DBALalso has this feature. They call it Transaction Nesting.
更新 2:Doctrine DBAL也有这个功能。他们称之为事务嵌套。
Like it or not, transactions are "global" and they do not obey object-oriented encapsulation.
不管喜欢与否,事务是“全局的”,它们不遵守面向对象的封装。
Problem scenario #1
问题场景 #1
I call commit(), are my changes committed? If I'm running inside an "inner transaction" they are not. The code that manages the outer transaction could choose to roll back, and my changes would be discarded without my knowledge or control.
我打电话给commit(),我的更改是否已提交?如果我在“内部事务”中运行,则它们不是。管理外部事务的代码可以选择回滚,而我的更改将在我不知情或无法控制的情况下被丢弃。
For example:
例如:
- Model A: begin transaction
- Model A: execute some changes
- Model B: begin transaction (silent no-op)
- Model B: execute some changes
- Model B: commit (silent no-op)
- Model A: rollback (discards both model A changes and model B changes)
- Model B: WTF!? What happened to my changes?
- 模型 A:开始交易
- 模型 A:执行一些更改
- 模型 B:开始事务(静默无操作)
- 模型 B:执行一些更改
- 模型 B:提交(无声无操作)
- 模型 A:回滚(丢弃模型 A 更改和模型 B 更改)
- B型:WTF!?我的改变发生了什么?
Problem scenario #2
问题场景#2
An inner transaction rolls back, it could discard legitimate changes made by an outer transaction. When control is returned to the outer code, it believes its transaction is still active and available to be committed. With your patch, they could call commit(), and since the transDepth is now 0, it would silently set $transDepthto -1 and return true, after not committing anything.
内部事务回滚,它可以丢弃外部事务所做的合法更改。当控制权返回给外部代码时,它认为其事务仍处于活动状态并且可以提交。使用您的补丁,他们可以调用commit(),并且由于 transDepth 现在为 0,它会$transDepth在不提交任何内容后默默地设置为 -1 并返回 true。
Problem scenario #3
问题场景 #3
If I call commit()or rollback()when there is no transaction active, it sets the $transDepthto -1. The next beginTransaction()increments the level to 0, which means the transaction can neither be rolled back nor committed. Subsequent calls to commit()will just decrement the transaction to -1 or further, and you'll never be able to commit until you do another superfluous beginTransaction()to increment the level again.
如果我打电话commit()或rollback()当没有活动的事务时,它会将 设置$transDepth为 -1。下一个beginTransaction()将级别增加到 0,这意味着事务既不能回滚也不能提交。后续调用commit()只会将事务减少到 -1 或更多,并且您将永远无法提交,直到您beginTransaction()再次执行另一个多余的操作以再次增加级别。
Basically, trying to manage transactions in application logic without allowing the database to do the bookkeeping is a doomed idea. If you have a requirement for two models to use explicit transaction control in one application request, then you must open two DB connections, one for each model. Then each model can have its own active transaction, which can be committed or rolled back independently from one another.
基本上,尝试在应用程序逻辑中管理事务而不允许数据库进行簿记是一个注定失败的想法。如果您需要两个模型在一个应用程序请求中使用显式事务控制,那么您必须打开两个数据库连接,每个模型一个。然后每个模型都可以有自己的活动事务,可以相互独立地提交或回滚。
回答by Bill Karwin
Do a try/catch: if the exception is that a transaction has already started (based on error code or the message of the string, whatever), carry on. Otherwise, throw the exception again.
执行 try/catch:如果异常是事务已经开始(基于错误代码或字符串的消息,无论如何),则继续。否则,再次抛出异常。
回答by Imran
Store the return value of beginTransaction() in Zend_Registry, and check it later.
将 beginTransaction() 的返回值存储在 Zend_Registry 中,稍后查看。
回答by Sean McSomething
Looking at the Zend_Db as well as the adapters (both mysqli and PDO versions) I'm not really seeing any nice way to check transaction state. There appears to be a ZF issueregarding this - fortunately with a patch slated to come out soon.
查看 Zend_Db 以及适配器(mysqli 和 PDO 版本),我真的没有看到任何检查事务状态的好方法。ZF似乎存在与此相关的问题- 幸运的是,补丁很快就会发布。
For the time being, if you'd rather not run unofficial ZF code, the mysqli documentationsays you can SELECT @@autocommitto find out if you're currently in a transaction (err... not in autocommit mode).
目前,如果您不想运行非官方的 ZF 代码,mysqli 文档说您可以SELECT @@autocommit查明您当前是否处于事务中(错误...不在自动提交模式中)。
回答by Dens
You can also write your code as per following:
您还可以按照以下方式编写代码:
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;
}
回答by curlyhairedgenius
For innoDB you should be able to use
对于 innoDB,您应该能够使用
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();
回答by Stephen Adelakun
This discussion is fairly old. As some have pointed out, you can do it in your application. PHP has a method since version 5 >= 5.3.3 to know if you are in the middle of a transaction. PDP::inTransaction() returns true or false. Link http://php.net/manual/en/pdo.intransaction.php
这个讨论是相当古老的。正如一些人指出的那样,您可以在您的应用程序中做到这一点。PHP 从版本 5 >= 5.3.3 开始有一种方法可以知道您是否处于事务中间。PDP::inTransaction() 返回真或假。链接http://php.net/manual/en/pdo.intransaction.php
回答by Pepstille
Maybe you can try PDO::inTransaction...returns TRUE if a transaction is currently active, and FALSE if not. I have not tested myself but it seems not bad!
也许您可以尝试 PDO::inTransaction...如果事务当前处于活动状态,则返回 TRUE,否则返回 FALSE。我没有测试过自己,但似乎还不错!
回答by Zac Imboden
I disagree with Bill Karwin's assessment that keeping track of transactions started is cockamamie, although I do like that word.
我不同意 Bill Karwin 的评估,即跟踪开始的交易是愚蠢的,尽管我确实喜欢这个词。
I have a situation where I have event handler functions that might get called by a module not written by me. My event handlers create a lot of records in the db. I definitely need to roll back if something wasn't passed correctly or is missing or something goes, well, cockamamie. I cannot know whether the outside module's code triggering the event handler is handling db transactions, because the code is written by other people. I have not found a way to query the database to see if a transaction is in progress.
我有一种情况,我的事件处理函数可能会被不是我编写的模块调用。我的事件处理程序在数据库中创建了很多记录。如果某些东西没有正确传递或丢失或发生了什么,我肯定需要回滚,好吧,cockamamie。我不知道触发事件处理程序的外部模块的代码是否正在处理数据库事务,因为代码是由其他人编写的。我还没有找到查询数据库以查看事务是否正在进行的方法。
So I DO keep count. I'm using CodeIgniter, which seems to do strange things if I ask it to start using nested db transactions (e.g. calling it's trans_start() method more than once). In other words, I can't just include trans_start() in my event handler, because if an outside function is also using trans_start(), rollbacks and commits don't occur correctly. There is always the possibility that I haven't yet figured out to manage those functions correctly, but I've run many tests.
所以我一直在数数。我正在使用 CodeIgniter,如果我要求它开始使用嵌套的 db 事务(例如多次调用它的 trans_start() 方法),它似乎会做一些奇怪的事情。换句话说,我不能只在我的事件处理程序中包含 trans_start(),因为如果外部函数也在使用 trans_start(),回滚和提交不会正确发生。总是有可能我还没有想出正确管理这些功能,但我已经运行了很多测试。
All my event handlers need to know is, has a db transaction already been initiated by another module calling in? If so, it does not start another new transaction and does not honor any rollbacks or commits either. It does trust that if some outside function has initiated a db transaction then it will also be handling rollbacks/commits.
我的所有事件处理程序需要知道的是,是否已由另一个调用的模块启动了 db 事务?如果是这样,它不会启动另一个新事务,也不支持任何回滚或提交。它确实相信,如果某个外部函数启动了一个数据库事务,那么它也将处理回滚/提交。
I have wrapper functions for CodeIgniter's transaction methods and these functions increment/decrement a counter.
我有 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;
}
In my situation, this is the only way to check for an existing db transaction. And it works. I wouldn't say that "The Application is managing db transactions". That's really untrue in this situation. It is simply checking whether some other part of the application has started any db transactions, so that it can avoid creating nested db transactions.
在我的情况下,这是检查现有数据库事务的唯一方法。它有效。我不会说“应用程序正在管理数据库事务”。在这种情况下,这真的是不真实的。它只是检查应用程序的其他部分是否启动了任何数据库事务,从而避免创建嵌套的数据库事务。
回答by Gregory Magarshak
In web-facing PHP, scripts are almost always invoked during a single web request. What you would really like to do in that case is start a transaction and commit it right before the script ends. If anything goes wrong, throw an exception and roll back the entire thing. Like this:
在面向 Web 的 PHP 中,脚本几乎总是在单个 Web 请求期间调用。在这种情况下,您真正想做的是启动一个事务并在脚本结束之前提交它。如果出现任何问题,抛出异常并回滚整个事情。像这样:
wrapper.php:
try {
// start transaction
include("your_script.php");
// commit transaction
} catch (RollbackException $e) {
// roll back transaction
}
The situation gets a little more complex with sharding, where you may be opening several connections. You have to add them to a list of connections where the transactions should be committed or rolled back at the end of the script. However, realize that in the case of sharding, unless you have a global mutex on transactions, you will not be easily able to achieve true isolation or atomicity of concurrent transactions because another script might be committing their transactions to the shards while you're committing yours. However, you might want to check out MySQL's distributed transactions.
使用分片时情况会变得稍微复杂一些,您可能会打开多个连接。您必须将它们添加到应在脚本末尾提交或回滚事务的连接列表中。但是,请注意,在分片的情况下,除非您对事务有全局互斥锁,否则您将无法轻松实现并发事务的真正隔离或原子性,因为在您提交时,另一个脚本可能会将其事务提交到分片你的。但是,您可能想查看 MySQL 的分布式事务。

