如何在 mysqli 中开始和结束事务?
Posted
技术标签:
【中文标题】如何在 mysqli 中开始和结束事务?【英文标题】:How to start and end transaction in mysqli? 【发布时间】:2012-08-18 23:44:20 【问题描述】:据我了解,事务在我们调用$mysqli->autocommit(FALSE);
语句后开始,并在调用$mysqli->commit();
命令后结束,如下例所示。
<?php
//Start transaction
$mysqli->autocommit(FALSE);
$mysqli->query('UPDATE `table` SET `col`=2');
$mysqli->query('UPDATE `table1` SET `col1`=3;');
$mysqli->commit();
//End transaction
//Executing other queries without transaction control
$mysqli->query("Select * from table1");
$mysqli->query("Update table1 set col1=2");
//End of executing other queries without transaction control
//Start transaction
$mysqli->autocommit(FALSE);
$mysqli->query('UPDATE `table` SET `col`=2');
$mysqli->query('UPDATE `table1` SET `col1`=3;');
$mysqli->commit();
//End transaction
?>
我理解正确了吗?如果不是,请纠正我,因为这实际上是我在现实生活中第一次使用交易。
谢谢。
【问题讨论】:
【参考方案1】:2020 年 11 月更新:@Dharman 给出了更好的答案,其中包含有关 mysqli 中事务的更多详细信息,只需检查一下即可:https://***.com/a/63764001/569101?
嗯,根据the php doc,你是对的。
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
/* check connection */
if (mysqli_connect_errno())
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
$mysqli->query("CREATE TABLE Language LIKE CountryLanguage");
/* set autocommit to off */
$mysqli->autocommit(FALSE);
/* Insert some values */
$mysqli->query("INSERT INTO Language VALUES ('DEU', 'Bavarian', 'F', 11.2)");
$mysqli->query("INSERT INTO Language VALUES ('DEU', 'Sw***', 'F', 9.4)");
/* commit transaction */
$mysqli->commit();
/* drop table */
$mysqli->query("DROP TABLE Language");
/* close connection */
$mysqli->close();
?>
在上面的例子中:
CREATE TABLE
是自动提交的,因为它是默认行为。
INSERT INTO
没有自动提交,因为 autocommit(FALSE)
。
DROP TABLE
是自动提交的,因为autocommit(FALSE)
被->commit();
重置。
【讨论】:
下面的@Patec:提交不会重新打开自动提交;见source 我知道你不能删除这个答案,但我想请你重新审视它并改进它。您复制的 PHP 手册页包含一个损坏的示例,该示例未显示如何正确使用事务。该页面现已被删除,并提供了一个新示例。因为您的答案是公认的答案,所以它描绘出这是正确的解决方案,但事实并非如此。请问,如果可以的话,你能改进一下吗? 哇,答案很老了。我看到你的答案是一个更好的答案,我没有时间改进我自己的答案。所以我编辑了我的答案以链接到你的答案。【参考方案2】:你认为commit
会自动将autocommit
切换回true
?
PHP Doc 中的评论说不!
【讨论】:
我不认为它会。直观地说,我假设“commit()”执行查询,而“autocommit()”将 mysqli 对象的自动提交属性切换为 true 或 false。 可以使用 $msqli->begin_transaction()、$mysqli->rollback() 和 $mysqli->commit();而不是显式关闭自动提交功能然后再重新打开。【参考方案3】:j0k 主要是对的,除了在 drop table 中。
->commit()
没有开启自动提交
相反,DROP TABLE 是一个 DDL 查询,并且 DDL 查询始终是隐式提交的,并将提交您之前未提交的所有工作。
因此,如果您没有提交工作,DDL 查询将强制提交。
【讨论】:
对于那些想知道的人:DDL 表示数据定义语言。 DDL、DQL、DML、DCL和TCL的区别见this article。 @Minding 几年前我可能应该提供那颗珍珠。我还应该声明$mysqli->commit();
不会神奇地打开自动提交。好吧,6 年过去了,我们知道我们应该回答更多问题:)【参考方案4】:
准备一次 SQL 语句,然后执行几次:
<?php
$Mysqli = new mysqli("host","user","pass","base");
// check connection
if(mysqli_connect_errno())
printf("Connect failed: %s\n",mysqli_connect_error());
exit();
// some data for db insertion
$countries=['Austria','Belgia','Croatia','Denmark','Estonia'];
// explicitly begin DB transaction
$Mysqli->begin_transaction();
// prepare statement (for multiple inserts) only once
$stmt=$Mysqli->prepare("INSERT INTO table(column) VALUES(?)");
// bind (by reference) prepared statement with variable $country
$stmt->bind_param('s',$country);
// load value from array into referenced variable $country
foreach($countries as $country)
//execute prep stat more times with new values
//$country is binded (referenced) by statement
//each execute will get new $country value
if(!$stmt->execute())
// rollback if prep stat execution fails
$Mysqli->rollback();
// exit or throw an exception
exit();
// close prepared statement
$stmt->close();
// commit transaction
$Mysqli->commit();
// close connection
$Mysqli->close();
?>
【讨论】:
【参考方案5】:如何在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 查询时
以及其他一些情况。欲了解更多信息,请参阅Statements That Cause an Implicit Commit
使用自动提交(假)
如果您关闭自动提交,您可以决定何时提交,但调用 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;
【讨论】:
您在begin_transaction()
的示例可以改进;如果有人将相同的数据两次插入表中,他们很可能希望该数据出现一次,而不是使用事务让它出现零次。 MySQL 事务的要点是,如果链接更新失败,它们会回滚有效更新,只是复制 SQL 插入并不能反映这一点。
@Martin 老实说,我认为我不完全理解您的评论,但如果可能的话,我想改进答案。我对最后一个示例做了一个小改动,以更清楚地显示哪个查询失败。如果您能多解释一下您的建议,我将不胜感激。
Transactions 的目的实际上是针对单个有效 SQL 操作的“组”;例如,通过填充订单篮表和订单主表来构建订单。因此,在movie
表中插入有效电影可能更合适,另一列引用电影制片人表,其中制片人姓名/出生日期等无效,因此如果电影制片人无效,则不添加电影。只是一个想法,因为这个主题(交易有用的现实生活示例)似乎很难找到实际示例。以上是关于如何在 mysqli 中开始和结束事务?的主要内容,如果未能解决你的问题,请参考以下文章
使用 spring 和 hibernate 时,如何处理会话/事务?
PHP mysqli::autocommit VS “开始交易”
PHP中的MySQLi扩展学习mysqli的事务与预处理语句