事务未生效需先确认autocommit是否被关闭:MySQLi/PDO默认开启,但begin_transaction后进入手动模式,必须显式commit/rollback;PDO须设ERRMODE_EXCEPTION防静默失败;MySQL中查INNODB_TRX可诊断卡住事务;MySQL不支持嵌套事务,应改用savepoint或统一事务边界。

事务没生效,先确认 autocommit 是否被意外关闭
PHP 中 MySQLi 或 PDO 默认开启自动提交(autocommit),但一旦执行 mysqli_begin_transaction() 或 $pdo->beginTransaction(),就进入手动事务模式——此后所有 SQL 都不会自动落库,直到显式调用 commit() 或 rollback()。常见错误是忘记写 commit(),或在异常分支里漏掉 rollback(),导致数据“消失”又查不到报错。
调试时第一件事:在事务块前后加日志,确认是否真的走到 commit();同时检查连接是否被复用且之前未清理状态。例如:
mysqli_begin_transaction($conn);
// ... 执行 insert/update
if ($success) {
mysqli_commit($conn); // 必须有
} else {
mysqli_rollback($conn); // 必须有,否则连接残留事务状态
}PDO 中 setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION) 必须开启
默认 PDO 错误模式是 PDO::ERRMODE_SILENT,SQL 报错只返回 false,不抛异常——这意味着事务中某条语句失败,后续语句仍会继续执行,commit() 也会照常调用,最终造成部分写入、部分丢弃,且无任何提示。
务必在创建 PDO 实例后立即设置:
立即学习“PHP免费学习笔记(深入)”;
$pdo = new PDO($dsn, $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- 不设这个,
INSERT INTO xxx VALUES (...) ON DUPLICATE KEY UPDATE出错会被吞掉 - 不设这个,外键约束失败、字段超长、NULL 插入 NOT NULL 字段等都静默失败
- PDOStatement::execute() 抛出的
PDOException可直接被捕获并触发rollback()
MySQL 层面检查事务是否卡住或被 kill
当 PHP 脚本崩溃、超时或被 nginx/fastcgi 终止,但 MySQL 连接未断开时,事务可能长期处于 ACTIVE 状态,阻塞其他操作。此时查不到 PHP 报错,但数据库变慢、SELECT ... FOR UPDATE 卡住、甚至出现死锁。
登录 MySQL 执行:
SELECT * FROM information_schema.INNODB_TRX\G
重点关注字段:
-
TRX_STATE= 'RUNNING' 但长时间不动 → 可能是 PHP 没 commit/rollback -
TRX_MYSQL_THREAD_ID对应线程 ID,可用KILL [thread_id]强制终止 -
TRX_QUERY显示当前执行语句,可判断卡在哪一步
注意:InnoDB 事务不支持跨连接传播,START TRANSACTION 只对当前连接有效,别指望一个请求里的事务影响另一个请求。
嵌套事务在 MySQL 中实际不生效,PHP 代码里别信 beginTransaction 嵌套
MySQL 本身不支持真正的嵌套事务(savepoint 是替代方案,但不是事务)。PDO 或 MySQLi 的 beginTransaction() 在已存在事务时只是静默返回 true,不会新建事务上下文。所以以下代码看似“嵌套”,实则无效:
$pdo->beginTransaction(); // 外层 doSomething(); $pdo->beginTransaction(); // 内层 —— 实际什么也没做 doOtherThing(); $pdo->commit(); // 只提交一次,不是两次 $pdo->commit(); // 第二次 commit 会报错:There is no active transaction
正确做法是统一用 savepoint:
$pdo->exec("SAVEPOINT sp1");
// ... 出错时
$pdo->exec("ROLLBACK TO SAVEPOINT sp1");或者更简单:PHP 层用标志位管理事务生命周期,避免重复 begin;所有业务逻辑共用同一事务边界,不要在函数内部擅自 begin/commit。
事务调试最麻烦的从来不是语法,而是状态残留和边界模糊——连着跑两次脚本,第二次失败,很可能是因为第一次没 rollback 干净。每次修改都要重连数据库,或确保异常路径 100% 覆盖 rollback。











