如何在 mysqli 中使用事务?
先决条件
为了使事务正常运行,您应该启用异常错误报告。否则mysqli不会报错,事务也不会正确执行。或者,您可以手动检查每个查询,但不建议这样做。要与 mysqli 正确连接,请使用以下 3 行:
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname');
$mysqli->set_charset('utf8mb4'); // always set the charset
事务仅适用于事务表。确保您的表存储引擎支持事务。例如,MyISAM 忽略提交/回滚。
交易
使用 mysqli 创建事务有两种可能的方法。默认情况下,所有查询/语句在执行后立即提交。您可以关闭自动提交或使用一次性事务。
在以下情况下,事务将提交到数据库:
- 打电话时
commit
- 设置 autocommit=1 后
- 开始另一笔交易时
- 执行 DDL 查询时
- 在其他一些情况下。有关更多信息,请参阅导致隐式提交的语句
使用自动提交(假)
如果您关闭自动提交,则您决定何时提交,但调用commit()
不会重新打开自动提交。
//Start transaction
$mysqli->autocommit(false);
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->commit();
// Changes are committed, but autocommit is not switched back on
// Following queries are still transactional.
// They will not be committed unless you call commit or switch autocommit back on
$mysqli->query('INSERT INTO director(name) VALUE("James Cameron")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Titanic';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->autocommit(true);
// All queries are committed and everything that follows will be immediately committed.
使用 begin_transaction()
您可以使用begin_transaction()
. 这没有设置autocommit=false
,所以当您调用commit()
您结束事务而不开始新事务时。
//Start transaction
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
$mysqli->commit();
// Changes are committed and the transaction has ended
// Following queries will be committed one by one as soon as they are peformed.
$mysqli->query('INSERT INTO director(name) VALUE("James Cameron")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Titanic';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
执行 DDL 语句
一些 SQL 语句会触发显式提交,但不会影响autocommit
.
//Start transaction
$mysqli->autocommit(false);
$mysqli->query('INSERT INTO director(name) VALUE("Steven Spielberg")');
$directorId = $mysqli->insert_id;
$movieTitle = 'Jurassic Park';
$stmt = $mysqli->prepare('INSERT INTO movie(title, directorId) VALUE(?,?)');
$stmt->bind_param('ss', $movieTitle, $directorId);
$stmt->execute();
// The following will call commit but it will not set autocommit=true
$mysqli->query('TRUNCATE TABLE movie_genre');
// if you want to switch autocommit back on, you have to call:
$mysqli->autocommit(true);
回滚
如果发生异常,则 PHP 将结束脚本的执行,并且代码将永远不会到达commit
语句。但是,在某些情况下,您可能希望显式回滚事务,例如避免在代码的其他地方意外调用 commit。
以下是此类交易的示例。第二个查询尝试插入到一个不存在的表中,这意味着 mysqli 将抛出异常。我们不是让 PHP 脚本死掉,而是捕获异常并回滚事务。该值4
将永远不会插入到数据库中,因为这两个查询都已回滚。
try {
// Start transaction
$mysqli->begin_transaction();
$mysqli->query('INSERT INTO some_table(col2) VALUE(4)');
$mysqli->query('INSERT INTO does_not_exist(col2) VALUE(4)');
// Commit changes
$mysqli->commit();
} catch (\Throwable $e) {
// Something went wrong. Rollback
$mysqli->rollback();
// Rethrow the exception so that PHP does not continue
// with the execution and the error can be logged in the error_log
throw $e;
}