商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > PHP怎样处理事务_PHP数据库事务处理【事务】

PHP怎样处理事务_PHP数据库事务处理【事务】

  发布于2026-07-03 阅读(0)

扫一扫,手机访问

先说一个很多人踩过的坑——不管是 mysqli 还是 PDO,默认情况下都不开启事务行为。也就是说,你不显式关闭自动提交、不调用开始事务的方法,那所有 SQL 执行完就立刻落库,这时候你写 rollback() 是没有任何效果的。很多人折腾半天回滚没反应,问题就出在这里。

PHP怎样处理事务_PHP数据库事务处理【事务】

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 的事务失效,九成是因为错误模式没设对

PDO 默认的错误模式是 PDO::ERRMODE_SILENT,这意味着 SQL 执行报错时,它只返回 false,不会抛异常。后果就是 catch 块根本抓不到任何错误,rollback() 也就永远不会被执行。这是最常见的问题,没有之一。

解决方案很明确:

  • 构造连接时,必须显式设置 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,
]);

想要局部回滚?用 SA VEPOINT,别指望“嵌套事务”

MySQL 本身不支持真正的嵌套事务。如果你连续多次调用 beginTransaction(),后面的调用要么被忽略,要么直接报错。想要实现“子事务回滚不影响父事务”的效果,只能靠保存点来模拟。

  • 在主事务中执行 $pdo->exec("SA VEPOINT sp1") 来设置一个保存点
  • 如果后续出错,用 $pdo->exec("ROLLBACK TO SA VEPOINT sp1") 回滚到该点
  • 保存点的名称必须唯一,而且不能跨事务边界使用——比如外层已经 commit 了,内层再去 ROLLBACK TO 肯定会失败
  • RELEASE SA VEPOINT sp1 可以显式释放保存点,但不是必需的。事务结束时,系统会自动清理所有保存点

必须提醒的是,保存点不是万能的。DDL 语句(比如 ALTER TABLE)会隐式提交当前事务,导致所有保存点失效。这一点在实际开发中很容易踩坑。

事务不是银弹:高并发场景下,光靠 commit/rollback 远远不够

拿库存扣减来举例:两个请求同时读到“剩余 10”,都判断可以扣减,结果就超卖了。事务本身并不解决“读-改-写”这个过程中的竞态问题,需要额外的保护措施。

  • SELECT ... FOR UPDATE 加行锁——但要注意,这只对 InnoDB 有效,而且必须在事务内部使用
  • 扣减前加库存校验,比如 WHERE qty >= ?,这样第二条 UPDATE 的影响行数就会是 0,你就可以据此判断发生了并发冲突
  • 结合唯一索引或幂等令牌来防止重复提交——这一点在支付回调、入库单等场景下尤其重要
  • 尽量避免长事务。锁持有时间越长,并发冲突的概率就越高。不要在事务内部做 HTTP 请求、文件操作这类耗时动作

最后还有一个容易被忽略的点:事务必须在单次 HTTP 请求的生命周期内完成闭环。PHP-FPM 的请求结束后,连接虽然可能被复用,但事务状态不会跨请求延续。千万别指望“前端分步提交、后端分步事务”这种方案,那基本是给自己挖坑。

本文转载于:https://www.php.cn/faq/2458206.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注