php数据库事务生效需同时满足:显式关闭自动提交、使用innodb引擎、异常模式设为抛出异常;任一缺失都将导致rollback失效。

PHP数据库事务不是“开了就自动生效”,而是必须显式关闭自动提交、正确捕获异常、并确保底层引擎支持——漏掉任一环节,rollback() 就是摆设。
mysqli 事务失效的三个硬性前提
很多开发者写了 $mysqli->begin_transaction() 却发现回滚不生效,根本原因不在代码写法,而在连接初始化阶段就已埋雷:
-
$mysqli->autocommit(false)必须在begin_transaction()之前调用;不关自动提交,所有query()仍会立刻落库 - MySQL 表必须使用
InnoDB引擎;MyISAM表执行begin_transaction()完全无意义 - 错误必须能被
catch捕获:如果 SQL 报错但没抛异常(比如用了mysqli_error()而非异常模式),rollback()就不会触发
推荐写法(PHP 7.0+):
$mysqli = new mysqli($host, $user, $pass, $db);
$mysqli->autocommit(false);
try {
$mysqli->begin_transaction();
$mysqli->query("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
$mysqli->query("UPDATE accounts SET balance = balance + 100 WHERE id = 2");
$mysqli->commit();
} catch (Exception $e) {
$mysqli->rollback();
throw $e;
} finally {
$mysqli->autocommit(true);
}
PDO 事务配置缺一不可的两个选项
PDO 的事务看似简单,但 90% 的失效案例都源于构造时漏配关键属性。仅写 $pdo->beginTransaction() 是不够的:
-
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION必须设置;否则 SQL 错误只返回false,catch根本捕不到 -
PDO::ATTR_AUTOCOMMIT => false必须显式关闭;否则beginTransaction()只是空操作,后续commit()和rollback()不起作用 -
commit()和rollback()都会把AUTOCOMMIT重置为true,所以连续事务需重复调用beginTransaction()
典型安全配置:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_AUTOCOMMIT => false,
]);
事务中读取数据时容易忽略的锁行为
事务里执行普通 SELECT 不会阻塞其他事务修改同一行——这不是 bug,是默认隔离级别(REPEATABLE READ)的设计。如果你需要“读完就锁住”,必须加 FOR UPDATE:
-
SELECT * FROM accounts WHERE id = 1→ 其他事务仍可并发更新该行 -
SELECT * FROM accounts WHERE id = 1 FOR UPDATE→ 该行被加写锁,直到当前事务commit()或rollback() - 不加
FOR UPDATE却依赖读取结果做后续更新(如“查余额→扣款”),可能引发超卖或负余额
示例(防超卖):
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT stock FROM products WHERE id = ? FOR UPDATE");
$stmt->execute([$product_id]);
$stock = $stmt->fetchColumn();
if ($stock >= $order_qty) {
$pdo->prepare("UPDATE products SET stock = stock - ? WHERE id = ?")->execute([$order_qty, $product_id]);
$pdo->commit();
} else {
$pdo->rollback();
}
Hyperf 等协程框架中的事务陷阱
在 Hyperf、Swoole 等常驻内存框架中,数据库连接复用导致事务状态极易污染:
- 一个请求开启事务但未
commit或rollback,连接归还到连接池后,下次被复用时可能残留_transaction_in_progress状态 - 框架提供的“自动事务”闭包方式(如
Db::transaction(...))内部做了状态清理,比手动调用beginTransaction()更安全 - 跨协程/多线程共享同一个
PDO实例调用rollback()是无效的——事务只对当前连接上下文有效
结论:在协程环境中,优先用框架封装好的事务方法,避免手写裸 beginTransaction();若必须手动控制,确保每个事务块都有明确的 finally 清理逻辑。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











