事务回滚无法正常工作

6
我已经用PDO系统做了一个数据库包装器,并在其基础上添加了额外的功能(是的,我知道这是对包装器的再次包装,但它只是在PDO基础上添加了一些额外的功能)。但我注意到了一个问题。
下面的代码并不能正常工作:
<?php
var_dump($db->beginTransaction());

$db->query('
 INSERT INTO test
 (data) VALUES (?)
 ;',
 array(
  'Foo'
 )
);
print_r($db->query('
 SELECT *
 FROM test
 ;'
)->fetchAll());

var_dump($db->rollBack());

print_r($db->query('
 SELECT *
 FROM test
 ;'
)->fetchAll());
?>

var_dump的结果显示beginTransaction和rollBack函数都返回了true,因此没有错误。

我原本期望第一个print_r调用会显示一个N项的数组,而第二个调用会显示N-1项。但事实并非如此,它们都显示相同数量的项。

我的 $db->query(< sql >, < values >) 调用实际上与 $pdo->prepare(< sql >)->execute(< values >) 一样(当然还有额外的错误处理)。

所以我认为或者MySQL的事务系统不起作用,或者PDO的实现有问题,或者我看错了些什么。

有人知道问题出在哪里吗?

5个回答

18

检查您的数据库类型是否为InnoDB。简单来说,您必须检查您的数据库是否支持事务。


这就是解决方案。但奇怪的是,为什么在MyISAM上使用事务时pdo不返回false(或抛出异常)...很奇怪。 - VDVLeon
你仍然可以在 MyISAM 表上使用事务,但是无法回滚。这意味着该事务不执行任何操作。甚至可以在同一事务中混合使用事务和非事务表 - 这只会让人感到混乱。通常(大多数情况下),你希望所有的表都是 innodb。 - MarkR

5

如果我要开始一个显式事务,为什么应该关闭自动提交? - Dmitry Pashkevich

3
我将其翻译为:

我以答案的形式输入这篇文章,因为评论太小了无法包含以下内容:

PDO只是对各种低级数据库接口库的包装。如果低级库没有抱怨,PDO也不会有问题。由于MySQL支持事务,任何事务操作都不会返回语法错误或其他错误。您可以在事务中使用MyISAM表,但对它们执行的任何操作都将像自动提交仍然处于活动状态一样执行:

mysql> create table myisamtable (x int) engine=myisam;
Query OK, 0 rows affected (0.00 sec)

mysql> create table innodbtable (x int) engine=innodb;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into myisamtable (x) values (1);
Query OK, 1 row affected (0.00 sec)

mysql> insert into innodbtable (x) values (2);
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select * from myisamtable;
+------+
| x    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

mysql> select * from innodbtable;
Empty set (0.00 sec)

mysql>

正如您所看到的,即使有交易正在进行,并且对MyISAM表执行了一些操作,也没有抛出任何错误。


1
查询成功,0行受影响,1个警告 (0.00秒)也许你可以检索是否有任何警告。 - oneat
1
好的,这是我重新进行测试插入/回滚后的警告输出:“一些非事务更改的表无法回滚” - Marc B

2

MySQL不支持对MyISAM表类型进行事务处理,而这恰恰是默认的表类型。

如果您需要进行事务处理,应该切换到InnoDB表类型。


0

这种情况发生的另一个原因是某些类型的SQL语句会导致立即自动提交。我有一个在事务中运行的大型脚本,但它被立即提交并忽略了事务。最终我发现是因为任何ALTER TABLE语句都会立即导致提交。

导致自动提交的语句类型包括:

  • 任何修改表或数据库的语句,例如ALTER TABLECREATE TABLE
  • 任何修改表权限的语句,例如ALTER USERSET PASSWORD
  • 任何锁定表或启动新事务的语句
  • 数据加载语句
  • 管理语句,例如ANALYZE TABLEFLUSHCACHE INDEX
  • 复制控制语句,例如与从库或主库相关的任何语句

更多信息和完整列表可在此处找到:https://dev.mysql.com/doc/refman/8.0/en/implicit-commit.html

如果您只在特定脚本中遇到此问题,并且确定正在使用InnoDB,则可能需要查看脚本中的任何SQL语句是否与这些匹配。


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