PDO 事务不工作

Posted

技术标签:

【中文标题】PDO 事务不工作【英文标题】:PDO Transaction not working 【发布时间】:2016-04-13 00:33:42 【问题描述】:

我有一个试图运行的 PDO 事务,第一个查询创建一个开关,第二个查询将有关它的信息添加到另一个表。我的问题是,由于某种原因,第一个查询没有正确执行,但事务已提交。 (我使用以下 PDO 类 http://culttt.com/2012/10/01/roll-your-own-pdo-php-class/)

try
    //Insert into required tables
    $db->beginTransaction();
    $db->Query("INSERT INTO firewall (Name)VALUES(:Name)");
    $db->bind(':Name',$Name);
    $db->execute();
    $db->Query("INSERT INTO firewall_switch (Switch_ID, firewall_id,customer_ID)VALUES(:Switch,LAST_INSERT_ID(),:Customer)");
    $db->bind(':Switch',$switch);
    $db->bind(':Customer',$customer);
    $db->execute();
    $db->endTransaction();
catch(PDOException $e)
    $db->cancelTransaction();

以下是从日志中在 SQL 中运行的内容:

6 Query       START TRANSACTION
6 Prepare     [6] INSERT INTO firewall (Name)VALUES(?)
6 Prepare     [7] INSERT INTO firewall_switch (Switch_ID, firewall_id,customer_ID)VALUES(?,LAST_INSERT_ID(),?)
6 Execute     [7] INSERT INTO firewall_switch (Switch_ID, firewall_id,customer_ID)VALUES('2',LAST_INSERT_ID(),'164')
6 Query       COMMIT

如您所见,第一个查询永远不会执行,但第二个会执行。此特定事务应该已回滚,因为存在不允许的重复 ID。

如果没有重复,则事务似乎按预期完成,但我不确定为什么回滚不起作用...

编辑:

数据库类: 类 Db

    private static $Connection = array();
    public $connection;

    private $dbh;
    private $error;

    private $stmt;

    public static function GetConnection($connection)
    
        if(!array_key_exists($connection,self::$Connection))
        
            $className = __CLASS__;
            self::$Connection[$connection] = new $className($connection);
        
        return self::$Connection[$connection];
    

    public function __construct($connection)

        global $config;
        //Load Settings


        $this->id = uniqid();
        $this->connection = $connection;

        if(array_key_exists($connection,$config['connections']['database']))
            $dbConfig = $config['connections']['database'][$connection];

            // Set DSN
            $dsn = 'mysql:host=' . $dbConfig['host'] . ';port='.$dbConfig['port'].';dbname=' . $dbConfig['database'];
        

        // Set options
        $options = array(
            PDO::ATTR_PERSISTENT    => true,
            PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
        );
        // Create a new PDO instantiate
        try
            $this->dbh = new PDO($dsn, $dbConfig['user'], $dbConfig['password'], $options);
        
        // Catch any errors
        catch(PDOException $e)
            $this->error = $e->getMessage();
            error_log($e->getMessage());
        
    
    //Create the SQL Query
    public function query($query)
        $this->stmt = $this->dbh->prepare($query);
    
    //Bind SQL Params
    public function bind($param, $value, $type = null)
        if (is_null($type)) 
          switch (true) 
            case is_int($value):
              $type = PDO::PARAM_INT;
              break;
            case is_bool($value):
              $type = PDO::PARAM_BOOL;
              break;
            case is_null($value):
              $type = PDO::PARAM_NULL;
              break;
            default:
              $type = PDO::PARAM_STR;
          
        
        $this->stmt->bindValue($param, $value, $type);
    
    //Execute the SQL
    public function execute($array = NULL)
        if($array == NULL)
            return $this->stmt->execute();
        else
            return $this->stmt->execute($array);
        

    
    public function resultset()
        $this->execute();
        return $this->stmt->fetchAll(PDO::FETCH_ASSOC);
    
    //Return Single Record
    public function single()
        $this->execute();
        return $this->stmt->fetch(PDO::FETCH_ASSOC);
    
    //Count rows in table
    public function rowCount()
        return $this->stmt->rowCount();
    
    //Show last ID Inserted into table
    public function lastInsertId()
        return $this->dbh->lastInsertId();
    
    //Transactions allows the tracking of multiple record inserts, should one fail all will rollback
    public function beginTransaction()
        return $this->dbh->beginTransaction();
    
    public function endTransaction()
        return $this->dbh->commit();
    
    public function cancelTransaction()
        return $this->dbh->rollBack();
    
    //Debug dumps the info that was contained in a perpared statement
    public function debugDumpParams()
        return $this->stmt->debugDumpParams();
    

?>

数据库结构:

CREATE TABLE `firewall` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `Name` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `Name_UNIQUE` (`Name`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=40 DEFAULT CHARSET=latin1 

CREATE TABLE `firewall_switch` (
  `id` int(11) NOT NULL auto_increment,
  `Switch_ID` int(10) unsigned NOT NULL,
  `firewall_id` int(10) unsigned NOT NULL,
  `Customer_ID` int(11) NOT NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `id_UNIQUE` (`id`),
  KEY `fk_firewall_switch_Switch1_idx` (`Switch_ID`),
  KEY `fk_firewall_switch_firewall1_idx` (`firewall_id`),
) ENGINE=InnoDB AUTO_INCREMENT=59 DEFAULT CHARSET=latin1 

【问题讨论】:

您能否发布您的$db 课程代码以及构建它的位置.. 该链接仅显示如何编写一个,它没有提供完整的课程,.重复检查的性质是什么?您能否在所引用的两个表中分别显示 SHOW CREATE TABLE 的结果。 你有设置 PDO 抛出异常吗?默认情况下,它使用静默错误模式,要求您​​在每次操作后明确检查错误。请参阅Errors and error handling 了解更多信息。 @Arth 附加了我的数据库类以及数据库结构 @eggyal 我在我的 DB 类中有这个 【参考方案1】:

好的,看来我已经找到了解决方案,看来像这样设置错误模式不起作用:

$options = array(
    PDO::ATTR_PERSISTENT    => true,
    PDO::ATTR_ERRMODE       => PDO::ERRMODE_EXCEPTION
);
try
        $this->dbh = new PDO($dsn, $dbConfig['user'], $dbConfig['password'], $options);

我现在已将其更改为:

try
    $this->dbh = new PDO($dsn, $dbConfig['user'], $dbConfig['password'], array(PDO::ATTR_PERSISTENT    => true));
    $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);    

【讨论】:

所以现在您可以看到为什么第一次插入从未执行过......出于兴趣,这是为什么? 我的理论是 $this->stmt 被覆盖 遗憾的是,这个误导性的答案吸引了如此多的 Google 访问者。【参考方案2】:

默认情况下,mysql 在启用自动提交的情况下运行,因此任何 mysql 查询都将被自动提交,尽管来自 pdo 的 begintransaction。

确保使用 mysql 命令关闭自动提交

  SET autocommit=0;

此外,如果您使用的 mysql 后端 (myisam) 不支持事务,则事务将无法正常工作。 (innodb 工作)

【讨论】:

我意识到这是一个旧评论,但想知道是否有理由反对。在文档中,它说自动提交由 PDO::beginTransaction 自动设置为 0。 php.net/manual/en/pdo.begintransaction.php【参考方案3】:

将第一个查询改为

INSERT INTO firewall (Name) VALUES (:Name)
    ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id);

这样,您随后使用 LAST_INSERT_ID 将起作用,无论 `:Name" 是否是一个副本。

警告:每次运行时都会“烧掉”一个 id,因此消耗 id 的速度会比预期的快。 id 可能是 INT UNSIGNED,你不太可能达到 40 亿。

(我以为手册中有讨论,但我找不到。所以我在https://dev.mysql.com/doc/refman/5.6/en/insert-on-duplicate.html添加了评论。)

【讨论】:

以上是关于PDO 事务不工作的主要内容,如果未能解决你的问题,请参考以下文章

了解 pdo mysql 事务

PDO:事务不回滚?

PDO事务处理

PHP PDO 事务?

PHP PDO Module 不提交事务,不上报原因

PDO / PHP / MySQL 中的性能:事务与直接执行