PHP数据库事务关键在于使用PDO或MySQLi扩展,显式关闭自动提交、调用beginTransaction()开启事务、执行操作后根据结果commit()或rollback();必须用InnoDB引擎,避免DDL语句和长事务,且事务须在单次HTTP请求内完成闭环。

PHP 中实现数据库事务,关键不在“能不能”,而在于你用的是哪种数据库扩展、是否显式开启了自动提交、以及有没有在出错时正确回滚。
mysqli 如何开启和控制事务
mysqli 默认开启自动提交(autocommit),必须手动关闭才能进入事务模式。不关 autocommit,begin_transaction() 或 query("START TRANSACTION") 之后执行的 SQL 仍会立即生效。
- 用
mysqli::autocommit(false)关闭自动提交,这是前提 - 推荐用
mysqli::begin_transaction()启动事务(PHP 7.0+),比手写START TRANSACTION更安全 - 执行完所有操作后,用
commit()提交;出错时必须调用rollback(),否则连接可能残留未完成事务 - 事务结束后建议恢复自动提交:
autocommit(true),避免影响后续独立查询
示例片段:
$mysqli = new mysqli($host, $user, $pass, $db);
$mysqli->autocommit(false);
<p>try {
$mysqli->begin_transaction();
$mysqli->query("INSERT INTO orders (...) VALUES (...)");
$mysqli->query("UPDATE stock SET qty = qty - 1 WHERE id = 123");
$mysqli->commit();
} catch (Exception $e) {
$mysqli->rollback();
throw $e;
} finally {
$mysqli->autocommit(true);
}</p>
PDO 开启事务的三个必要动作
PDO 的事务控制看似简单,但漏掉任意一步都会让事务失效:禁用自动提交 + 显式开始 + 显式结束(commit/rollback)。
-
PDO::ATTR_AUTOCOMMIT必须设为false(构造时或通过setAttribute()) - 不能只依赖
beginTransaction()—— 如果 autocommit 仍是 true,它只是个空操作 -
commit()和rollback()都会隐式重置 autocommit 状态为 true,所以如果要连续事务,每次都要重新beginTransaction() - 注意 PDO 默认错误模式是
PDO::ERRMODE_SILENT,务必设成PDO::ERRMODE_EXCEPTION,否则 SQL 错误不会抛异常,catch捕不到
典型配置与使用:
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_AUTOCOMMIT => false,
]);
<p>try {
$pdo->beginTransaction();
$pdo->exec("INSERT INTO logs (...) VALUES (...)");
$pdo->prepare("UPDATE accounts SET balance = ? WHERE id = ?")->execute([$new_bal, $id]);
$pdo->commit();
} catch (PDOException $e) {
$pdo->rollback();
// 记录错误,不要静默吞掉
}</p>
事务失败却不回滚?检查这几点
最常见的情况不是代码写错,而是环境或语句本身绕过了事务机制。
- 使用了不支持事务的存储引擎(如 MyISAM),哪怕写了
BEGIN也无效 —— 必须用 InnoDB 或其他事务型引擎 - 执行了 DDL 语句(如
CREATE TABLE、ALTER TABLE),MySQL 会隐式提交当前事务 - 在事务中调用了存储过程,而该过程内部有
COMMIT或START TRANSACTION,会导致外部事务被截断 - PHP 进程崩溃、超时或被 kill,未执行到
rollback()—— 此时依赖 MySQL 的 wait_timeout 或 innodb_rollback_on_timeout 配置,但不可靠,应用层应尽量保证逻辑覆盖
跨请求事务为什么行不通
HTTP 是无状态协议,一次请求对应一个 PHP 进程生命周期。你在第一个请求里 begin_transaction(),第二个请求拿到的是全新数据库连接,不可能继续同一个事务。
- 试图用 session 存 connection 对象或事务 ID 完全无效 —— 连接资源无法序列化跨请求复用
- 长连接(persistent connection)也不能解决:每个请求仍会初始化新事务上下文
- 真正需要跨步骤提交的场景(如分步下单),得靠业务层状态机 + 补偿事务(Saga 模式),而不是指望数据库事务撑过多个 HTTP 请求
事务必须在一个请求内完成闭环,边界清晰。想“暂停事务等用户确认”,本质是设计错了 —— 应该把确认前的状态存为草稿,确认后再启动真正事务。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











