使用以下代码可以实现 MySQL 中的 PHP“嵌套”事务?

     2023-03-14     111

关键词:

【中文标题】使用以下代码可以实现 MySQL 中的 PHP“嵌套”事务?【英文标题】:PHP "Nested" transactions in MySQL could be a reality using the following code? 【发布时间】:2017-02-09 20:34:23 【问题描述】:

好的,我正在使用 PHP 在 MySQL 中寻找“嵌套”事务的解决方案,正如您在 MySQL 文档中所知道的那样,事务中不可能有事务 (Mysql transactions within transactions)。我试图使用http://php.net/manual/en/pdo.begintransaction.php 中提出的 Database 类,但不幸的是,这对我来说是错误的,因为它的计数器范围是 object level 而不是 class level,来解决这个问题我创建了这个类(TransactionController),它的计数器(名为$nest)是静态的,它带来了使事务“线性”所需的类级别(我说的是“线性”:它显然是嵌套的,但如果你看起来很它不是嵌套的,那么事务将运行良好,你怎么看?(看最后的例子,车主)

        class TransactionController extends \\PDO 
            public static $warn_rollback_was_thrown = false;
            public static $transaction_rollbacked = false;
            public function __construct()
            
                parent :: __construct( ... connection info ... );
            
            public static $nest = 0;
            public function reset()
            
                TransactionController :: $transaction_rollbacked = false;
                TransactionController :: $warn_rollback_was_thrown = false;
                TransactionController :: $nest = 0;
            
            function beginTransaction()
            
                $result = null;
                if (TransactionController :: $nest == 0) 
                    $this->reset();
                    $result = parent :: beginTransaction();
                
                TransactionController :: $nest++;
                return $result;
            

            public function commit()
            

                $result = null;

                if (TransactionController :: $nest == 0 &&
                        !TransactionController :: $transaction_rollbacked &&
                        !TransactionController :: $warn_rollback_was_thrown) 
                            $result = parent :: commit();
                        
                        TransactionController :: $nest--;
                        return $result;
            

            public function rollback()
            
                $result = null;
                if (TransactionController :: $nest >= 0) 
                    if (TransactionController :: $nest == 0) 
                        $result = parent :: rollback();
                        TransactionController :: $transaction_rollbacked = true;
                    
                    else 
                        TransactionController  :: $warn_rollback_was_thrown = true;
                    
                
TransactionController :: $nest--;
                return $result;
            

    public function transactionFailed()
    
        return TransactionController :: $warn_rollback_was_thrown === true;
    
    // to force rollback you can only do it from $nest = 0
    public function forceRollback()
    
        if (TransactionController :: $nest === 0) 
            throw new \PDOException();

    
        

        class CarData extends TransactionController 
            public function insertCar()
            

                try 
                    $this->beginTransaction();
                    ... (operations) ...
                    $this->commit();
                
                catch (\PDOException $e) 
                    $this->rollback();
                
            
        
        class PersonData extends TransactionController 
            public function insertPerson(  $person=null )
            
                try 
                    $this->beginTransaction();
                    ... (operations) ...
                    $this->commit();
                
                catch (\PDOException $e) 
                    $this->rollback();
                
            
        

        class CarOwnerData extends TransactionController 
            public function createOwner()
            
                try 
                    $this->beginTransaction();

                    $car = new CarData();
                    $car->insertCar();

                    $person = new PersonData();
                    $person->insertPerson();

                    ... (operations) ...

                    $this->commit();
                
                catch (\PDOException $e) 
                    $this->rollback();
                
            
        


        $sellCar = new CarOwnerData();
        $sellCar->createOwner();

UPDATE1static attribute $warn_rollback_was_thrown 被添加到 TransactionController 以警告事务在执行的某个时刻失败,但没有回滚。

UPDATE2:当事务在某个时刻失败时,您可以让代码仍然运行到最后或使用forceRollback() 明确停止它,例如,请参见以下代码:

<?php    // inside the class PersonData

    public function insertMultiplePersons( $arrayPersons )
    
        try 
        $this->beginTransaction();
        if (is_array( $arrayPersons )) 
            foreach ($arrayPersons as $k => $person) 
                $this->insertPerson( $person ); 
                if ($this->transactionFailed()) 
                    $this->forceRollback();                    
                
            
        
        $this->commit();
        
        catch (\PDOException $e) 
            $this->rollback();
        
     ?>

【问题讨论】:

除了在rollback 之后缺少$nest 的重置:嵌套事务应该支持:start trans 1, do something, start trans 2, rollback trans 2, optionally retry trans 2 or do something else, commit trans 1,你的代码不能支持这个(因为 mysql 不支持它)。如果您从不回滚,您的代码只会按预期工作。每当发生任何内部回滚时,您都需要跳转到 trans 1 的异常(您可以通过重新引发异常来执行此操作),否则在 $person-&gt;insertPerson(); 失败后,代码 ... (operations) ... 将在没有任何事务的情况下执行。 这个想法不是回滚或提交嵌套事务,只是在级别 0 处回滚或提交所有,您需要更好地理解代码。出于这个原因,我说:事务看起来像是嵌套的(php),但实际上在 mysql 执行中它不是嵌套的。看着挺(没有丢失重置,因为有一个$transaction_rollbacked = true 你的意思是,在你编辑之后它会:-P(在它真的没有之前,它只会调用 $parent->rollback,实际上,它仍然不起作用)。但至少现在很清楚你想要做什么。您的代码现在看起来更好了。我认为$this-&gt;beginTransaction(); 应该是$parent-&gt;beginTransaction(); (否则它永远不会调用pdo,而是做一个循环)。 rollback 中的 TransactionController :: $nest--; 必须在 == 0 的测试之前(否则它永远不会回滚),对于 commit 也是如此。并且forceRollback 应该设置一个变量,您在rollback 中检查并重新抛出... 通过使用静态类变量,您刚刚阻止自己在任何给定请求期间拥有多个数据库连接(例如到不同的数据库)。 我将在下面发布一个答案,并附上我的意思的代码示例。 【参考方案1】:

代码中插入的逻辑必须改变。

不必要的循环是影响性能的最糟糕的事情。

当您知道要插入多个人并且他们可以通过一个查询插入时。不要在循环内做。只需使用一个查询即可。这是多重插入的主要语​​法:

INSERT INTO table_name (col1,col2,col3,...)
VALUES (Value1,Value2,...), (Value1,Value2,...)

insertPerson 方法必须处理多人。像这样:

$this->insertPerson($arrayPersons);

在 insertPerson 方法中,您必须创建 VALUES,就像我之前在这里解释的那样:How to insert multiple dynamic rows into the database

然后,insertPerson 方法可以在 ONE QUERY 中插入一个人或多个人。

【讨论】:

这不能回答问题。 你不能说更好的解决方案不能回答问题,而你想用更复杂和低性能的解决方案来解决问题!让我告诉你一个故事。在肥皂产品线中,出现了故障。一些框空到最后一行。包装机太贵了,他们无力更换或修理它,他们雇了一些人在最后一行之前检查那些盒子。一位智者在包装机导轨和空箱飞出导轨后立即放了一个风扇,因为空箱很轻。也许一个问题有 100 个答案,但请选择快速可靠的答案。 嵌套事务的问题与多次插入的问题无关。 可能还有其他查询需要包含在事务中。 @ICE 你的解决方案相当于智者加快包装机的速度,同时空箱在生产中堆积如山。这个“答案”应该是评论。至于“不必要的循环是最糟糕的事情会影响性能”,不确定这是真的。【参考方案2】:

正如@YourCommonSense 在 cmets 中指出的那样,您实际上并没有实现嵌套事务。

我不确定我是否喜欢在代码中的任何位置调用 commit() 并且它实际上并没有提交任何内容。

您的整个解决方案似乎是试图减轻将事务代码放入您的插入函数并忘记它的设计决策。

您可以将插入操作与事务逻辑分开,并将这些函数调用包装在一个单独的函数中,该函数执行事务:

public/private function insertPerson(  $person=null )

  ... (operations) ...


public function createPerson()

    $person = new Person();
    ... (setup person) ...

    $this->beginTransaction();
    try 
        $this->insertPerson($person);
        $this->commit();
    
    catch (\PDOException $e) 
        $this->rollback();
    
 

如果您绝对确定需要始终在事务中插入人员,则可以在调用事务时检查您是否在事务中:

public/private function insertPerson($person=null)

  if (!$this->hasActiveTransaction) // Needs implementing
     throw new Exception('Must be called within a transaction');
  
  ...(operations)...

在我们的项目中,所有的保存逻辑都在模型中,所有事务逻辑都在控制器级别。

我假设您知道对于单个语句不需要事务,因为这些是原子操作,并且您的代码代表更复杂的情况。

【讨论】:

+1 我同意在控制器级别管理事务是在 MVC 架构中有意义的唯一方法。【参考方案3】:

我在上面发表了评论:

通过使用静态类变量,您刚刚阻止自己在任何给定请求期间拥有多个数据库连接(例如到不同的数据库)。

你似乎对我的评论有疑问:

@BillKarwin 您的意思可能是 $nest 计数器对于每个数据库连接都是静态的。 ——克里斯蒂安·克里什克

这不是static 在 PHP 中的工作方式。静态类属性由类的所有实例共享。如果一个实例更新它,所有其他实例都会看到更改。

<?php

class Foo 
 static $nest = 0;

 public function getNest() 
  return Foo::$nest;
 

 public function setNest($newNest) 
  Foo::$nest = $newNest;
 



$foo1 = new Foo();
$foo2 = new Foo();

echo "foo1::nest = " . $foo1->getNest() . "\n";
echo "foo2::nest = " . $foo2->getNest() . "\n";

$foo1->setNest(42);

echo "foo1::nest = " . $foo1->getNest() . "\n";
echo "foo2::nest = " . $foo2->getNest() . "\n";

输出:

foo1::nest = 0
foo2::nest = 0
foo1::nest = 42
foo2::nest = 42

这意味着您的静态$nest 类属性对于您的应用程序中的所有数据库连接都是相同的值。因此,您当前的设计不能有多个数据库连接。

我什至不知道您为什么将此属性设为静态。没必要。

但我同意@ICE 的回答,即尝试实现这种“嵌套事务”类是愚蠢的。它不起作用。事务的范围是数据库连接,而不是对象。早在 2008 年,我就曾在 Stack Overflow 上写过这个问题。阅读我的回答:How do detect that transaction has already been started?

【讨论】:

您的回答可能是解决该问题的建议(这显然是正确的),我相信如果您有一个带有连接的静态数组和计数器,它不会产生任何冲突。也许这是考虑这个主题并回答这个问题的最终代码。我正在努力...感谢您的回答。

如何使用 PHP 和 MySQL 创建 JSON 嵌套子父树(PDO 方法)

】如何使用PHP和MySQL创建JSON嵌套子父树(PDO方法)【英文标题】:HowtocreateJSONnestedchildparenttreewithPHPandMySQL(PDOmethod)【发布时间】:2016-01-2408:42:27【问题描述】:我正在尝试使用PHP和MySQL构建嵌套的父子JSON树。我的目标是从我的MySQ... 查看详情

如何快速批量删除mysql数据库中的数据表

一、使用phpmyadmin工具批量删除mysql数据库表使用phpmyadmin数据库管理工具进行删除,这是一个传统的方法,在任何php虚拟主机中,你都可以操作。下面是操作过程介绍:1、登录phpmyadmin。选择你的mysql数据库名进入——点击结构—... 查看详情

怎样用php实现两个mysql数据库的同步

...将A数据库中的a表复制到B数据库中的a表中,求详细代码使用程序无法实现这种功能,因为无法保证事务的一致性,比如:A数据库中的a表复制到B数据库中的a表的过程中,A数据库中的a表的一条记录被删除,这样就无法实现数据... 查看详情

PHP MySQL 查询中的 MySQL/Apache 错误

...】:我收到以下错误:拒绝用户\'apache\'@\'localhost\'访问(使用密码:否)使用以下代码时:<?phpinclude("../includes/connect.php");$query="SELECT*fromstory" 查看详情

PHP/MYSQL - 使用 php 计算和比较 mysql 表中的条目

】PHP/MYSQL-使用php计算和比较mysql表中的条目【英文标题】:PHP/MYSQL-Countingandcomparingentriesinamyqsltablewithphp【发布时间】:2021-10-1100:04:11【问题描述】:我想知道是否有人可以帮助我/指出正确的方向。我有一个数据库,其中包含一... 查看详情

php中的数组分页实现(非数据库)(代码片段)

在日常开发的业务环境中,我们一般都会使用MySQL语句来实现分页的功能。但是,往往也有些数据并不多,或者只是获取PHP中定义的一些数组数据时需要分页的功能。这时,我们其实不需要每次都去查询数据库,可以在一次查询... 查看详情

php代码中的错误mysql更新命令

...检查与您对应的手册MySQL服务器版本,在\'10:07:28WHERE附近使用正确的语法id 查看详情

使用 CSS 和 PHP/MySQL 的动态下拉菜单

】使用CSS和PHP/MySQL的动态下拉菜单【英文标题】:DynamicdropdownmenususingCSSandPHP/MySQL【发布时间】:2015-06-3013:30:04【问题描述】:我想使用PHP和MySQL创建一个动态下拉菜单。菜单还可以,但不是我想要的方式。我想要这样的菜单如下... 查看详情

mysql数据库进阶篇(代码片段)

...键字和ALL小结SELECT的FROM嵌套子查询SELECT中的EXISTS子查询使用子查询复制 查看详情

使用 PHP 显示存储在 mySQL 数据库中的图像

】使用PHP显示存储在mySQL数据库中的图像【英文标题】:DisplayanimagestoredinmySQLdatabasewithPHP【发布时间】:2015-10-2607:32:09【问题描述】:我有一个包含blob图像的数据库。我想在网页中显示它们。我正在使用以下php代码来获取图像。... 查看详情

使用 PHP 和 MySQL 的引导分页

】使用PHP和MySQL的引导分页【英文标题】:BootstrapPaginationusingPHP&MySQL【发布时间】:2015-01-1501:21:34【问题描述】:我是Bootstrap的新手,我正在尝试在页面中的某个部分上实现分页以正确表示数据。有人可以帮忙吗?这里是代码... 查看详情

嵌套子菜单的 AngularJs 本机幻灯片切换无法正确更新高度

...:我正在为AngularJS中的嵌套菜单实现滑动切换。我一直在使用以下页面中的代码:https://blog.assaf.co/native-slide-t 查看详情

mysql序列使用(代码片段)

...#xff0c;如果你想实现其他字段也实现自动增加,就可以使用MySQL序列来实现。本章我们将介绍如何使用MySQL的序列。使用AUTO_INCREMENTMySQL中最简单使用序列的方法就是使用MySQLAUTO_INCREMENT来定义列。实例以下实例中创建了数据表ins... 查看详情

使用 PHP 将多个 MySQL 结果添加到 HTML 表中的一个单元格

】使用PHP将多个MySQL结果添加到HTML表中的一个单元格【英文标题】:AddmultipleMySQLresultstoonecellinaHTMLtableusingPHP【发布时间】:2015-03-1323:20:51【问题描述】:我目前正在使用以下代码来获取数据,以便在主页的内容部分以我希望的... 查看详情

在 MYSQL 中使用 IN 会导致 PHP 中的变量出现问题

】在MYSQL中使用IN会导致PHP中的变量出现问题【英文标题】:UsingINinMYSQLcauseissueswithvariablesinPHP【发布时间】:2016-05-1414:58:27【问题描述】:在网站上工作时,我发现PDO存在一个相当奇怪的问题。以下代码仅选择用户1发布的帖子,... 查看详情

使用mysql中的访问列在php中设置用户角色时出错

】使用mysql中的访问列在php中设置用户角色时出错【英文标题】:Errorinsettinguserrolesinphpusingaccesscolumninmysql【发布时间】:2018-06-0705:11:22【问题描述】:我有一个登录/注册网站。管理员和用户都在mysql的同一个表中。我的用户表如... 查看详情

mysql存储过程使用游标实现两张表数据同步数据(代码片段)

...能用在存储过程和函数中,在存储过程和函数中可以使用游标对结果集进行循环的处理,可以遍历返回的多行结果,每次拿到一整行数据。1.2游标的语法使用1.创建游标DECLARE游标名称CURSORFOR查询sql语句; 如 declarestu_da... 查看详情

php创建xml文件并使用mysql中的数据填充(代码片段)

查看详情