前面我们介绍了 MySQL 中的事务处理,其实在 PDO 中也是支持事务的,而且在 PDO 中使用事务很容易。前面我们已经介绍了构建和执行 SQL 语句的方式,当需要使用事务时,只要在执行 SQL 语句前使用 PDO::beginTransaction() 方法来启动事务即可。如果数据库底层驱动不支持事务,则抛出一个 PDOException 异常。事务一旦启动,可以使用 PDO::commit() 方法来提交事务,或者使用 PDO::rollBack() 方法来回滚事务。
PDO::beginTransaction() 方法的作用是启动一个事务;当事务中的所有 SQL 语句全部执行成功后,可以使用 PDO::commit() 方法来结束并提交一个事务;如果事务中某个 SQL 语句执行失败了,那么可以使用 PDO::rollBack() 方法来结束并回滚事务,事务中的所有 SQL 语句都将无效。注意,事务中的 SQL 语句要么全部成功执行,要么根本就不执行。
事务通常是通过把一批要执行的 SQL 语句“积蓄”起来,然后使之同时生效而,这样做的好处就是可以大大地提高这些 SQL 语句的执行的效率。换句话说,事务可以使脚本更快,而且更加健壮。
需要注意的是,并非每种数据库都支持事务,因此当第一次打开连接时,PDO 需要在“自动提交”模式下运行。自动提交模式意味着,如果数据库支持,运行的每个查询都有它自己的隐式事务,如果数据库不支持事务,则没有。
提示:PDO 仅在驱动层检查是否具有事务处理能力。如果某些运行时条件意味着事务不可用,且数据库服务接受请求去启动一个事务,PDO::beginTransaction() 将仍然返回 TRUE 而且没有错误。
当脚本结束或连接即将被关闭时,如果尚有未完成的事务,那么 PDO 将自动回滚这个事务。这种安全措施有可以避免在脚本意外终止时出现数据不一致的情况。也就是说,如果没有显式地提交事务,那么当某个地方出错时,将执行回滚来保证数据安全。
下面通过一个简单示例来演示以下 PDO 中事务的使用。假设有 userA 和 userB 两个银行账户,现在使用 userA 账户向 userB 账号中转账,使用事务来保证 userA 账户中转出一定金额的同时,userB 账户中转入相等的金额。
存放 userA 和 userB 两个账户的数据表的表结构如下所示:
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(45) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '用户名',
`cash` decimal(9, 2) NOT NULL COMMENT '账户余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
INSERT INTO `account` VALUES (1, 'userA', 100000.00);
INSERT INTO `account` VALUES (2, 'userB', 90000.00);
具体的实现代码如下所示:
<?php
$dsn = "mysql:dbname=testdb;host=localhost";
$user = "root";
$password = "root";
$dbh = new PDO($dsn, $user, $password);
//使用事务之前,要先关闭自动提交。不关闭的话,出现异常的时候没法回滚。
//据手册描述,ATTR_AUTOCOMMIT 属性只在 mysql、OCI(oracle)、firebird 三种数据库中可用
$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 0);
$price = 996;
try {
$dbh->beginTransaction();
//用户A账户扣除指定的金额
$sqlcmd = "UPDATE account SET cash=cash-$price WHERE name='userA'";
$affected_rows = $dbh->exec($sqlcmd);
if ($affected_rows > 0) {
echo "用户A账户扣除成功" . "<br>";
} else {
throw new Exception("用户A账户扣除失败");
}
//用户B账户增加指定的金额
$affected_rows = $dbh->exec("UPDATE account SET cash=cash+$price WHERE name='userB'");
if ($affected_rows > 0) {
echo "用户B账户增加成功" . "<br>";
} else {
throw new Exception("用户B账户增加失败");
}
echo "转账成功";
//若前面两个步骤都成功,则提交事务
$dbh->commit();
}catch (PDOException $e) //若前面两个步骤中出现了异常,则回滚
{
echo $e->getMessage();
$dbh->rollback();
}
//对事务的使用结束之后,重新开启自动提交
$dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, 1);
?>