PHP + MySQL交易示例


294

我真的还没有找到使用MySQL事务的PHP文件的正常示例。你能给我简单的例子吗?

还有一个问题。我已经做了很多编程工作,并且没有使用事务。我可以放置PHP函数或其他东西吗?header.php如果其中一个mysql_query失败了,那么其他人也失败了?


我想我已经知道了,对吗?:

mysql_query("SET AUTOCOMMIT=0");
mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}

10
您可以使用mysql_query("BEGIN");序列而不是序列mysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla 2012年

75
请不要mysql_*在新代码中使用函数。它们不再维护,已正式弃用。看到红色框了吗?了解准备的语句来代替,并使用 PDO库MySQLi -本文将帮助你决定哪些。如果您选择PDO,这是一个很好的教程
Naftali又名Neal,

6
是否执行“ mysql_query(“ SET AUTOCOMMIT = 0”);“ 设置所有连接以等待提交功能还是仅用于其相关连接?
哈米德2014年

1
@Neal,mysql尽管被弃用,但实际上还是死了,它将在PECL中永久可用。
Pacerier,2014年

2
@Pacerier不赞成使用的东西不会“死亡”。它们是针对旧版软件而正式持有的,但不再维护,也不会因新软件的建议做法而受到损害。事实仍然存在,不要使用mysql
taylorcressy 2014年

Answers:


325

我在处理事务时通常使用的想法是这样的(半伪代码)

try {
    // First of all, let's begin a transaction
    $db->beginTransaction();

    // A set of queries; if one fails, an exception should be thrown
    $db->query('first query');
    $db->query('second query');
    $db->query('third query');

    // If we arrive here, it means that no exception was thrown
    // i.e. no query has failed, and we can commit the transaction
    $db->commit();
} catch (Exception $e) {
    // An exception has been thrown
    // We must rollback the transaction
    $db->rollback();
}


请注意,根据这种想法,如果查询失败,则必须引发Exception:

  • PDO可以做到这一点,具体取决于您的配置方式
  • 否则,使用某些其他API,您可能必须测试用于执行查询的函数的结果,并自己引发异常。


不幸的是,没有涉及魔术。您不能只是在某处放置指令并自动完成事务:您仍然必须指定必须在事务中执行哪一组查询。

例如,通常您会在交易之前(在之前begin)有几个查询,在交易之后(在commitrollback之后)有另外几个查询,并且无论这些事件发生(或不发生,您都希望执行这些查询。交易。


35
如果执行的操作可能会引发除数据库异常以外的异常,请务必小心。如果是这样,则非db语句中的异常可能会无意中导致回滚(即使所有db调用都成功)。通常,您认为回滚是一个好主意,即使该错误不在db端,但有时第3方/非关键代码可能会导致不太重要的异常,并且您仍然希望继续交易。
HalilÖzgür2012年

6
这里是什么$db类型?mysqli?
杰克

3
@Jake有关使用mysqli(风格类似于Pascal的方法)的示例,请参见我的答案。
EleventyOne

2
PDOException如果需要,可以轻松地对其进行修改以捕获甚至检查异常值。us2.php.net/PDOException
Yamiko

1
$ db是PDO对象(连接)。参考:php.net/manual/en/pdo.connections.php
Fil

110

我想我已经知道了,对吗?:

mysql_query("START TRANSACTION");

$a1 = mysql_query("INSERT INTO rarara (l_id) VALUES('1')");
$a2 = mysql_query("INSERT INTO rarara (l_id) VALUES('2')");

if ($a1 and $a2) {
    mysql_query("COMMIT");
} else {        
    mysql_query("ROLLBACK");
}

26
无需设置autocommit = 0。交易始终以这种方式工作。
bgcode

2
@babonk-不确定InnoDB是这种情况吗?
buggedcom 2011年

6
我认为,一旦您开始交易,它的工作就好像AUTOCOMMIT = 0
bgcode 2011年

4
@babonk是正确的。一旦启动了事务,就隐式设置了AUTOCOMMIT = 0,并且在事务通过提交或回滚结束后,MySql会回退开始事务之前使用的AUTOCOMMIT值。注意:不应设置AUTOCOMMIT = 0,因为在提交更改后,如果您决定插入/更新另一行,则应显式提交。
eroteev

4
引擎存储应该是InnoDB,而不是MyISAM!
javad

39
<?php

// trans.php
function begin(){
    mysql_query("BEGIN");
}

function commit(){
    mysql_query("COMMIT");
}

function rollback(){
    mysql_query("ROLLBACK");
}

mysql_connect("localhost","Dude1", "SuperSecret") or die(mysql_error());

mysql_select_db("bedrock") or die(mysql_error());

$query = "INSERT INTO employee (ssn,name,phone) values ('123-45-6789','Matt','1-800-555-1212')";

begin(); // transaction begins

$result = mysql_query($query);

if(!$result){
    rollback(); // transaction rolls back
    echo "transaction rolled back";
    exit;
}else{
    commit(); // transaction is committed
    echo "Database transaction was successful";
}

?>

对于像这样的广泛而引人注目的问题,如果答案也能反映出这一点,那就太好了。您的代码示例很棒,但是您可以详细说明吗?解释交易,为什么,何时何地?最后,将代码链接至您的解释。
丹尼斯·哈尔布林克

3
欢迎使用StackOverflow。请务必在回答中写一些描述性文字。
阿德里安·海涅

6
抱歉,我是begginer,而且我的英语不好,它的代码示例非常简单-对于begginers-commit()rollback()begin()放在类DB中(例如),$ query-不会一次-可能是$ query0 $ query1-然后them他们-我使用此代码,这非常容易理解=)
Gedzberg Alex

20
他的评论使这个例子很清楚。好的代码不需要描述文本。问题也要求一个简单的例子。我喜欢这个答案。

@GedzbergAlex对于单个查询不需要事务,只是对事务感到困惑,是否有理由将事务用于单个查询?
ʞɔıɥʇɹɐʞouɐɯ

35

由于这是在Google上“ php mysql transaction”的第一个结果,我想我要添加一个答案,明确说明如何使用mysqli进行此操作(作为原始作者想要的示例)。这是使用PHP / mysqli进行事务处理的简化示例:

// let's pretend that a user wants to create a new "group". we will do so
// while at the same time creating a "membership" for the group which
// consists solely of the user themselves (at first). accordingly, the group
// and membership records should be created together, or not at all.
// this sounds like a job for: TRANSACTIONS! (*cue music*)

$group_name = "The Thursday Thumpers";
$member_name = "EleventyOne";
$conn = new mysqli($db_host,$db_user,$db_passwd,$db_name); // error-check this

// note: this is meant for InnoDB tables. won't work with MyISAM tables.

try {

    $conn->autocommit(FALSE); // i.e., start transaction

    // assume that the TABLE groups has an auto_increment id field
    $query = "INSERT INTO groups (name) ";
    $query .= "VALUES ('$group_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    $group_id = $conn->insert_id; // last auto_inc id from *this* connection

    $query = "INSERT INTO group_membership (group_id,name) ";
    $query .= "VALUES ('$group_id','$member_name')";
    $result = $conn->query($query);
    if ( !$result ) {
        $result->free();
        throw new Exception($conn->error);
    }

    // our SQL queries have been successful. commit them
    // and go back to non-transaction mode.

    $conn->commit();
    $conn->autocommit(TRUE); // i.e., end transaction
}
catch ( Exception $e ) {

    // before rolling back the transaction, you'd want
    // to make sure that the exception was db-related
    $conn->rollback(); 
    $conn->autocommit(TRUE); // i.e., end transaction   
}

另外,请记住,PHP 5.5具有新方法mysqli :: begin_transaction。但是,PHP团队尚未对此进行记录,并且我仍然停留在PHP 5.3中,因此无法对此发表评论。


2
在相关说明中,我刚刚发现,如果您使用的是InnoDB表,则可以在使用autocomitt()方法进行事务处理时锁定/解锁表,但是在使用begin_transaction()方法时是不可能的:MySQL文档
EleventyOne 2013年

+1为带有实际mysqli代码的详细(和注释)示例。谢谢你 关于锁定/事务的观点确实很有趣。
a.real.human.being

1
“ autocommit(FALSE)”会影响到同一数据库/表中的另一个连接吗?我的意思是,如果我们打开两个页面,其中一个页面将其连接设置为“ autocommit(FALSE)”,而另一页面则保留了autocommit功能,它是否等待提交功能。我想知道自动提交是否是连接的属性,而不是数据库/表的属性。感谢
Hamid 2014年

2
$conn->autocommit(FALSE)在上面的示例中,@ Hamid 仅影响单个连接-它对与数据库的任何其他连接没有影响。
EleventyOne 2014年

1
注意:如果查询能够返回评估为false或零的有效结果,则if (!result)应该执行if (result === false),而不是。
ToolmakerSteve

10

请检查您使用的存储引擎。如果是MyISAM,则Transaction('COMMIT','ROLLBACK')不会支持,因为只有InnoDB存储引擎而不是MyISAM支持事务。


7

使用PDO连接时:

$pdo = new PDO('mysql:host=localhost;dbname=mydb;charset=utf8', $user, $pass, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // this is important
]);

我经常使用以下代码进行事务管理:

function transaction(Closure $callback)
{
    global $pdo; // let's assume our PDO connection is in a global var

    // start the transaction outside of the try block, because
    // you don't want to rollback a transaction that failed to start
    $pdo->beginTransaction(); 
    try
    {
        $callback();
        $pdo->commit(); 
    }
    catch (Exception $e) // it's better to replace this with Throwable on PHP 7+
    {
        $pdo->rollBack();
        throw $e; // we still have to complain about the exception
    }
}

用法示例:

transaction(function()
{
    global $pdo;

    $pdo->query('first query');
    $pdo->query('second query');
    $pdo->query('third query');
});

这样,事务管理代码就不会在整个项目中重复。这是一件好事,因为从该线程中其他PDO授予的答案来看,很容易在其中犯错误。最常见的是忘记重新抛出异常并在try块内开始事务。


5

我做了一个函数来获取查询向量并进行事务处理,也许有人会发现它有用:

function transaction ($con, $Q){
        mysqli_query($con, "START TRANSACTION");

        for ($i = 0; $i < count ($Q); $i++){
            if (!mysqli_query ($con, $Q[$i])){
                echo 'Error! Info: <' . mysqli_error ($con) . '> Query: <' . $Q[$i] . '>';
                break;
            }   
        }

        if ($i == count ($Q)){
            mysqli_query($con, "COMMIT");
            return 1;
        }
        else {
            mysqli_query($con, "ROLLBACK");
            return 0;
        }
    }

3

我有这个,但是不确定这是否正确。也可以尝试一下。

mysql_query("START TRANSACTION");
$flag = true;
$query = "INSERT INTO testing (myid) VALUES ('test')";

$query2 = "INSERT INTO testing2 (myid2) VALUES ('test2')";

$result = mysql_query($query) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

$result = mysql_query($query2) or trigger_error(mysql_error(), E_USER_ERROR);
if (!$result) {
$flag = false;
}

if ($flag) {
mysql_query("COMMIT");
} else {        
mysql_query("ROLLBACK");
}

这里的想法:http : //www.phpknowhow.com/mysql/transactions/


代码不正确。trigger_error将返回true(除非您弄糟了您的调用),因此$ result始终为true,因此此代码将错过任何失败的查询,并始终尝试提交。同样令人困扰的是,即使您链接到使用的教程,您仍在使用旧的不推荐使用的方法mysql_query,而不是mysqli使用mysqli。恕我直言,您应该删除此错误的示例,或替换为使用phpknowhow教程中编写的代码。
ToolmakerSteve

2

使用的另一个过程样式示例mysqli_multi_query假设$query用分号分隔的语句填充。

mysqli_begin_transaction ($link);

for (mysqli_multi_query ($link, $query);
    mysqli_more_results ($link);
    mysqli_next_result ($link) );

! mysqli_errno ($link) ?
    mysqli_commit ($link) : mysqli_rollback ($link);

1
有点奇怪的代码,但至少建议使用mysqli_multi_query。任何对此感兴趣的人都应该在其他地方使用Google搜索,以获取更干净的示例。
ToolmakerSteve
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.