PDO 事务不工作
PDO Transaction not working
我有一个 PDO 事务,我正在尝试 运行,第一个查询创建一个开关,第二个查询将有关它的信息添加到另一个 table。我的问题是,由于某种原因,第一个查询没有正确执行,但事务已提交。 (我使用以下 PDO Class 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。
如果没有重复项,那么事务似乎会按预期完成,但我不确定为什么回滚不起作用...
编辑:
数据库Class:
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
默认情况下 mysql 在启用自动提交的情况下运行,因此尽管来自 pdo 的 begintransaction,任何 mysql 查询都将自动提交。
确保使用 mysql 命令关闭自动提交
SET autocommit=0;
此外,如果您使用的 mysql 后端 (myisam) 不支持事务,那么事务将无法正常工作。 (innodb 有效)
好的,看来我找到了解决办法,看来像这样设置错误模式是行不通的:
$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);
}
将第一个查询更改为
INSERT INTO firewall (Name) VALUES (:Name)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id);
这样一来,无论 `:Name" 是否为伪造,您随后对 LAST_INSERT_ID
的使用都将起作用。
警告:每次 运行 时都会 'burn' 一个 ID,因此 ID 的消耗速度会比预期的要快。大概id
是INT UNSIGNED
,你不太可能达到40亿。
(我以为这在手册中有讨论,但我找不到。所以我在 https://dev.mysql.com/doc/refman/5.6/en/insert-on-duplicate.html 中添加了评论。)
我有一个 PDO 事务,我正在尝试 运行,第一个查询创建一个开关,第二个查询将有关它的信息添加到另一个 table。我的问题是,由于某种原因,第一个查询没有正确执行,但事务已提交。 (我使用以下 PDO Class 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。
如果没有重复项,那么事务似乎会按预期完成,但我不确定为什么回滚不起作用...
编辑:
数据库Class: 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
默认情况下 mysql 在启用自动提交的情况下运行,因此尽管来自 pdo 的 begintransaction,任何 mysql 查询都将自动提交。
确保使用 mysql 命令关闭自动提交
SET autocommit=0;
此外,如果您使用的 mysql 后端 (myisam) 不支持事务,那么事务将无法正常工作。 (innodb 有效)
好的,看来我找到了解决办法,看来像这样设置错误模式是行不通的:
$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);
}
将第一个查询更改为
INSERT INTO firewall (Name) VALUES (:Name)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id);
这样一来,无论 `:Name" 是否为伪造,您随后对 LAST_INSERT_ID
的使用都将起作用。
警告:每次 运行 时都会 'burn' 一个 ID,因此 ID 的消耗速度会比预期的要快。大概id
是INT UNSIGNED
,你不太可能达到40亿。
(我以为这在手册中有讨论,但我找不到。所以我在 https://dev.mysql.com/doc/refman/5.6/en/insert-on-duplicate.html 中添加了评论。)