PHP MySQL 与 MySQLi 事务处理详解及注意事项

Cora
Cora
发布时间:2026-10-14 | 6人浏览过
原创

PHP 中的数据库操作常涉及多步执行的业务逻辑,此时需要利用事务保证数据一致性。本文将深入对比原生 mysql 函数与 mysqli 对象在事务处理上的实现方式,剖析自动提交(autocommit)机制陷阱,并解释为何某些 SQL 语句无法被回滚。同时,针对默认不支持事务的存储引擎,提供启用 InnoDB 支持的具体配置方法。

为什么需要使用事务

在数据库操作中,多条 SQL 语句往往具有逻辑关联。例如转账或积分更新,若其中一步失败,后续步骤不应执行,且已执行的步骤需撤销,以保证数据状态的完整性。这就是事务(Transaction)的核心作用:确保一组操作要么全部成功,要么全部失败。

需要注意的是,MySQL 默认的存储行为是在每条 SQL 语句执行后自动提交(Commit)。这意味着每条语句独立成为一个事务。为了实现多步操作的原子性,必须手动关闭自动提交模式或使用显式的事务启动语句。

准备条件:存储引擎的选择

在使用事务前,必须确认使用的数据表是否支持事务。MySQL 的不同存储引擎对事务的支持情况如下:

  • InnoDB:支持 ACID 事务、行级锁和高并发,是 Web 应用中最常用的支持事务的引擎。
  • BDB:Berkeley DB,也支持事务,但在现代 MySQL版本中较少使用。
  • MyISAM:不支持事务。它主要用于只读程序以提高性能。如果数据库默认引擎是 MyISAM,则需要手动切换为 InnoDB。

如何开启 InnoDB 支持:部分服务器安装时可能禁用了 InnoDB。若发现不支持事务,可尝试以下步骤:

  1. 编辑 MySQL 配置文件(如 /etc/mysql/my.cnf 或 Windows 下的 my.ini),注释掉或移除 skip-innodb 参数。
  2. 重启 MySQL 服务。
  3. 通过 phpMyAdmin 或命令行执行 SHOW ENGINES;,确认 InnoDB 状态为 YES。

Mysqli 方式处理事务

MySQLi 提供了面向对象的方式封装了事务操作,使用 autocommit(false) 开始事务,配合 commit() 和 rollback() 完成或回滚。这种方式结构清晰,错误判断通过 $mysqli->errno 进行。

第1步:初始化连接并关闭自动提交

建立数据库连接后,调用 autocommit(false) 禁止每条语句自动提交,从而允许我们将多个查询组合在一个事务中。

$mysqli = new mysqli('localhost', 'root', '', 'DB_Lib2Test');
$mysqli->autocommit(false); // 开始事务,关闭自动提交

第2步:执行业务 SQL 语句

在此阶段执行所有预期的数据库操作,如更新、插入等。

$sql1 = "UPDATE User SET ScoreCount = ScoreCount + 10 WHERE ID='123456'";
$sql2 = "UPDATE ScoreDetail SET FScore = 300 WHERE ID='123456'";

第3步:检查错误并提交或回滚

执行完毕后,检查 $mysqli->errno。如果无错误(值为 0),则提交事务;否则回滚。

$mysqli->query($sql1);
$mysqli->query($sql2);

if (!$mysqli->errno) {
    $mysqli->commit();
    echo 'Success';
} else {
    echo 'Error';
    $mysqli->rollback();
}

PHP mysql_* 系列函数处理事务

虽然 mysql_* 系列函数已被废弃且不建议在新项目中使用,但了解其事务实现原理有助于理解底层机制。该系列函数通过发送 SQL 指令 'start transaction' 或 'SET autocommit=0' 来手动控制事务。

以下示例展示了如何使用 mysql_query 发起事务:

$conn = mysql_connect('localhost', 'root', '');
mysql_select_db('DB_Lib2Test');

// 方法1: 显式启动事务
mysql_query('START TRANSACTION');

// 方法2: 关闭自动提交
// mysql_query('SET autocommit=0');

$sql1 = "UPDATE User SET ScoreCount = ScoreCount + 10 WHERE ID='123456'";
$sql2 = "UPDATE ScoreDetail SET FScore = 300 WHERE ID='123456'";

mysql_query($sql1);
mysql_query($sql2);

if (mysql_errno()) {
    mysql_query('ROLLBACK');
    echo 'Error';
} else {
    mysql_query('COMMIT');
    echo 'Success';
}

// 恢复自动提交状态
// mysql_query('SET autocommit=1');

注意事项:自动提交的陷阱

如果在执行事务期间,意外执行了一条未包含在事务块内的 SQL(例如直接执行了一个插入语句),且没有关闭自动提交,该语句会立即生效并不可回滚。因此,在使用事务时,务必确保相关 SQL 都在同一事务上下文中执行,或者在必要时显式设置 SET autocommit=0。

特殊现象:DDL 语句与事务回滚

在进行复杂的数据迁移或表结构调整时,开发者常遇到“事务似乎失效”的情况。这是因为 DDL(数据定义语言)语句会自动触发 COMMIT,导致之前未提交的操作提前结束。

以下 DDL 语句在执行时会隐式提交当前事务:

  • ALTER TABLE, CREATE TABLE, DROP TABLE
  • RENAME TABLE
  • TRUNCATE TABLE
  • LOCK TABLES, UNLOCK TABLES
  • SET AUTOCOMMIT=1
  • START TRANSACTION(本身也是一个提交触发点)

案例分析:Rename 导致的隐式提交

假设我们尝试通过创建新表并重命名的方式安全地修改表结构,并将这些操作包裹在事务中:

$mysqli = new mysqli('localhost', 'root', '', 'DB_Lib2Test');
$mysqli->autocommit(false);

$sql1 = 'CREATE TABLE ScoreDetail_new (id INT)';
$sql2 = 'RENAME TABLE ScoreDetail TO ScoreDetail_bak';
$sql3 = 'RENAME TABLE ScoreDetail_new TO ScoreDetail';

$mysqli->query($sql1);
$mysqli->query($sql2);
// 如果此处模拟一个错误,或者继续执行...
$mysqli->query($sql3);

if (!$mysqli->errno) {
    $mysqli->commit();
    echo 'Success';
} else {
    echo 'Error';
    $mysqli->rollback();
}

在上述代码中,即便 $sql2 失败或未执行,$sql1 创建的表依然存在于数据库中。这是因为 RENAME TABLE 在执行时,MySQL 会先自动提交之前未完成的操作(包括 CREATE TABLE),然后再执行重命名。因此,DDL 语句不能参与普通的事务回滚。在设计依赖事务保护的安全方案时,应尽量避免混合使用 DDL 和 DML,或使用存储过程及临时表策略规避此问题。

简要总结

在处理 PHP 数据库操作时,推荐使用 MySQLi 或 PDO 进行事务管理。务必确认使用的是 InnoDB 引擎,因为 MyISAM 不支持事务。同时,要警惕 autocommit 默认开启带来的副作用,以及 DDL 语句引发的隐式提交风险,合理规划 SQL 执行顺序以确保数据的一致性。

以上就是 PHP MySQL与MySQLi事务处理及相关注意事项的详细内容,更多关于PHP数据库操作优化的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表