PDO事务不起作用。

11

我有一个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类:class 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结果吗? - Arth
你是否设置了PDO抛出异常?默认情况下,它使用静默错误模式,需要您在每个操作后显式地检查错误。有关更多信息,请参见错误和错误处理 - eggyal
@Arth,附上我的数据库类和数据库结构。 - Steven Marks
@eggyal,我在我的DB类中得到了这个。 - Steven Marks
3个回答

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

现在你可以看到为什么第一个插入语句从未执行...出于好奇,那是为什么? - eggyal
我的理论是 $this->stmt 被覆盖了。 - PauAI
遗憾的是,这个误导性的答案吸引了很多来自谷歌的访问者。 - Your Common Sense

-1

默认情况下,MySQL 运行时启用自动提交,因此无论 PDO 中的 begintransaction 是否存在,任何 MySQL 查询都将被自动提交。

请确保使用 MySQL 命令将自动提交关闭。

  SET autocommit=0;

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


我知道这是一个旧评论,但我想知道是否downvotes是有理由的。在文档中,它说autocommit会被PDO :: beginTransaction自动设置为0。https://www.php.net/manual/en/pdo.begintransaction.php - enobrev

-1

将第一个查询更改为

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

这样,无论“:Name”是否是重复的,您随后使用LAST_INSERT_ID的方式都将起作用。

注意:每次运行它都会“消耗”一个ID,因此ID将比预期更快地被使用。可能idINT UNSIGNED,您不太可能达到40亿。

(我认为在手册中已经讨论过这个问题,但我找不到相关内容。因此,我在https://dev.mysql.com/doc/refman/5.6/en/insert-on-duplicate.html中添加了一条注释。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接