
本文详解如何通过 try-catch 结合 mysqli 的 `begin_transaction()`、`commit()` 和 `rollback()` 实现原子性操作:任一 sql 插入失败时,自动撤销所有已执行语句,确保数据一致性。
在 PHP 中使用 MySQLi 执行多条 INSERT 操作时,仅调用 begin_transaction() 和 commit() 并不能自动处理错误回滚——因为 commit() 会无条件提交所有已成功执行的语句,而语法错误(如表名拼写错误 IINSERT)会导致 mysqli_query() 返回 false,但程序仍继续执行后续代码,最终调用 commit(),造成部分数据残留,破坏事务的原子性。
✅ 正确做法是:将事务逻辑包裹在 try...catch 块中,并主动检测 SQL 执行结果或捕获异常。MySQLi 在启用异常模式后,会将 SQL 错误抛出为 mysqli_sql_exception,从而触发回滚:
options(MYSQLI_OPT_EXCEPTION, true);
try {
$connection->begin_transaction();
// 成功插入
mysqli_query($connection, "INSERT INTO categories (name) VALUES ('Electronics')");
// 故意写错:表名不存在或语法错误 → 抛出 mysqli_sql_exception
mysqli_query($connection, "IINSERT INTO categories (name) VALUES ('Books')");
// 仅当全部成功才提交
$connection->commit();
echo "✅ 所有操作已提交";
} catch (mysqli_sql_exception $e) {
// 发生任何 SQL 错误时立即回滚
$connection->rollback();
echo "❌ 事务已回滚。错误:" . $e->getMessage();
}
?>⚠️ 注意事项:
- 必须提前调用 $connection->options(MYSQLI_OPT_EXCEPTION, true),否则 mysqli_query() 在失败时仅返回 false,不会触发 catch;
- 不要混合使用面向过程(mysqli_query())和面向对象($connection->commit())风格——推荐统一使用面向对象方式(如 $connection->query(...)),避免隐式连接状态混淆;
- rollback() 仅对当前未提交的事务生效;若已 commit() 或连接关闭,则无法回滚;
- 对于主键冲突、唯一索引违规等运行时错误,同样会被 mysqli_sql_exception 捕获,因此该方案覆盖语法错误与逻辑约束错误两类典型场景。
总结:事务不是“自动容错机制”,而是需显式控制的编程契约。只有结合异常捕获 + 显式 rollback(),才能真正实现“全成功才提交,任一失败即撤销”的 ACID 保证。
立即学习“PHP免费学习笔记(深入)”;











