mysqli和PDO都需显式关闭自动提交并调用开始事务方法才能真正启用事务;mysqli须先设autocommit(false),PDO则必须配置ERRMODE_EXCEPTION和AUTOCOMMIT=false,否则rollback无效。

mysqli 和 PDO 都能处理事务,但默认都不开启事务行为——不显式关闭自动提交、不调用开始事务方法,所有 SQL 会立即生效,rollback() 形同虚设。
mysqli 必须先关 autocommit(false) 才能真正开启事务
很多开发者写了 $mysqli->begin_transaction() 却发现回滚无效,根本原因是:mysqli 默认是自动提交模式,begin_transaction() 只是“声明要开事务”,但若 autocommit 仍为 true,每条 query() 仍会立刻落库。
- 必须在连接后第一时间调用
$mysqli->autocommit(false) - 推荐用
begin_transaction()(PHP 7.0+),比手写START TRANSACTION更安全、可读性更好 -
commit()和rollback()后,建议手动恢复autocommit(true),避免影响后续独立查询 - 若使用
multi_query(),需配合next_result()检查每条语句状态,不能只看第一条返回值
示例关键片段:
$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 的事务失效,90% 是因为没设 PDO::ATTR_ERRMODE
PDO 默认错误模式是 PDO::ERRMODE_SILENT,SQL 报错只返回 false,不会抛异常,导致 catch 完全捕不到错误,rollback() 根本不会执行。
立即学习“PHP免费学习笔记(深入)”;
- 构造时必须显式设置:
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - 同时建议设置
PDO::ATTR_AUTOCOMMIT => false,避免依赖beginTransaction()的隐式开关 -
commit()和rollback()都会重置autocommit为true,所以连续事务需重复beginTransaction() - 不要用多个
PDO实例管理同一事务,跨对象调用rollback()无效
典型配置:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_AUTOCOMMIT => false,
]);
嵌套逻辑想局部回滚?用 SAVEPOINT,别信“嵌套事务”
MySQL 不支持真正的嵌套事务,beginTransaction() 连续调用会被忽略或报错。所谓“子事务回滚不影响父事务”,只能靠保存点模拟:
- 在主事务中执行
$pdo->exec("SAVEPOINT sp1") - 后续出错时用
$pdo->exec("ROLLBACK TO SAVEPOINT sp1") -
sp1名称必须唯一,且不能跨事务边界使用(比如外层已commit,内层再ROLLBACK TO会失败) -
RELEASE SAVEPOINT sp1可显式释放,但非必需;事务结束时自动清理
注意:保存点不是万能的——DDL 语句(如 ALTER TABLE)会隐式提交当前事务,导致所有保存点失效。
事务不是银弹:进销存等高并发场景下,光靠 commit/rollback 不够
比如库存扣减,两个请求同时读到“剩余 10”,都判断可扣,结果超卖。事务本身不解决“读-改-写”竞态,需额外手段:
- 用
SELECT ... FOR UPDATE加行锁(仅对 InnoDB 有效,且必须在事务内) - 扣减前加库存校验(
WHERE qty >= ?),让第二条UPDATE影响行为 0 - 结合唯一索引或幂等令牌防重复提交(尤其支付回调、入库单)
- 避免长事务:锁持有时间越长,并发冲突概率越高;不要在事务里做 HTTP 请求、文件操作等耗时动作
最易被忽略的一点:事务必须在单次 HTTP 请求生命周期内完成闭环。PHP-FPM 请求结束后连接可能复用,但事务状态不会跨请求延续——别指望“前端分步提交、后端分步事务”。



















