最近我一直在测试我的PHP框架的数据库包装类,它是基于PHP数据对象。我已经成功地通过了Oracle数据库的所有测试,当我遇到一个似乎是ACID噩梦的bug时,我开始用MySQL做测试。
简而言之,我的数据库驱动程序包装类执行以下操作:
1)使用以下属性建立持久数据库连接
<>之前self::$connection = new PDOdsn美元, DATABASE_USERNAME, DATABASE_PASSWORD, (PDO::ATTR_AUTOCOMMIT => FALSE//不自动提交每条语句,PDO::ATTR_CASE => PDO::CASE_LOWER//强制列名小写,PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC//返回结果集为按列名索引的数组,PDO:: attr_emulate_preparates => (DATABASE_DRIVER == 'mysql')//只允许mysql模拟准备好的语句,PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION//抛出错误并回滚事务,PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING//将空字符串转换为NULL,PDO::ATTR_PERSISTENT => TRUE//使用持久连接]);之前2) Wrapper类有一个execute()
方法,它是运行各种SQL语句的主干。当使用execute()
方法执行SQL语句时,它使用PDO::inTransaction()
方法检查事务是否活动。如果没有,则开始事务。下面是这个方法的样子(跳过所有无聊的部分):
3)到目前为止一切顺利。但是让我们看一下下面的例子,它对同一个表调用DELETE语句和SELECT语句,条件非常相同:
<>之前database::execute('DELETE FROM dms_test WHERE id = 5');$data = database::execute('SELECT FROM dms_test WHERE id = 5');之前4)每个人都希望SELECT语句返回一个空的结果集,因为之前的DELETE语句只是删除了同一事务中的所有数据。
5)但是,尽管听起来很疯狂,SELECT语句返回非空的结果集,就像从来没有发出过DELETE语句一样。
有趣的是,同样的例子在Oracle数据库中也能正常工作。你知道MySQL有什么问题吗?你们中有人遇到过类似的问题吗?
我能够通过完全删除PDO的事务机制并用我自己的机制代替它来解决这个问题。显然,使用PDO事务机制与MySQL RDBMS和禁用自动提交模式可能会导致不可预测的行为。我不知道这是PDO还是MySQL的bug。
如果你想使用PDO实现开箱即用的事务访问数据库,不要使用PDO内置的PDO::beginTransaction()
, PDO::commit()
和PDO::rollback()
方法。
相反,我建议您使用以下方法:
-
建立连接时,使用属性
PDO::ATTR_AUTOCOMMIT => FALSE
-
通过声明自己的变量来跟踪事务中的状态(如
$in_transaction
) -
注册调用本地数据库
ROLLBACK
的关闭函数请求结束(如果处于事务状态)
使用这种方法,我能够克服上面提到的错误。
auto commit设置为false,因此除非您使用事务、提交或回滚提交,否则不会保存任何更改PDO::ATTR_AUTOCOMMIT => FALSE