Ví dụ về giao dịch PHP + MySQL


294

Tôi thực sự không tìm thấy ví dụ bình thường về tệp PHP nơi giao dịch MySQL đang được sử dụng. Bạn có thể chỉ cho tôi ví dụ đơn giản về điều đó?

Và một câu hỏi nữa. Tôi đã thực hiện rất nhiều chương trình và không sử dụng giao dịch. Tôi có thể đặt một hàm PHP hoặc một cái gì đó trong header.phpđó nếu một cái mysql_querythất bại, thì những cái khác cũng thất bại không?


Tôi nghĩ rằng tôi đã tìm ra nó, phải không?:

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
Bạn có thể sử dụng mysql_query("BEGIN");thay vì trình tựmysql_query("SET AUTOCOMMIT=0"); mysql_query("START TRANSACTION");
Kirzilla

75
Xin vui lòng, không sử dụng các mysql_*chức năng trong mã mới . Chúng không còn được duy trì và chính thức bị phản đối . Thấy hộp đỏ không? Thay vào đó, hãytìm hiểu về các báo cáo đã chuẩn bị và sử dụng PDO hoặc MySQLi - bài viết này sẽ giúp bạn quyết định. Nếu bạn chọn PDO, đây là một hướng dẫn tốt .
Naftali aka Neal

6
Có "mysql_query (" SET AUTOCOMMIT = 0 ");" đặt tất cả các kết nối để chờ chức năng cam kết hay chỉ là cho kết nối liên quan của nó?
Hamid

1
@Neal, Thật ra mysqlwun chết mặc dù bị phản đối, nó sẽ có sẵn trong PECL mãi mãi.
Pacerier

2
@Pacerier Những thứ bị phản đối không "chết". Chúng được tổ chức chính thức cho phần mềm cũ nhưng không còn được duy trì và bị ảnh hưởng từ bất kỳ thực tiễn được đề xuất nào cho phần mềm mới. Thực tế vẫn còn, không sử dụngmysql
taylorcressy

Câu trả lời:


325

Ý tưởng tôi thường sử dụng khi làm việc với các giao dịch trông như thế này (mã bán giả) :

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();
}


Lưu ý rằng, với ý tưởng này, nếu một truy vấn không thành công, một ngoại lệ phải được ném:

  • PDO có thể làm điều đó, tùy thuộc vào cách bạn định cấu hình nó
  • khác, với một số API khác, bạn có thể phải kiểm tra kết quả của hàm được sử dụng để thực hiện truy vấn và tự ném ngoại lệ.


Thật không may, không có phép thuật liên quan. Bạn không thể chỉ cần đặt một hướng dẫn ở đâu đó và thực hiện các giao dịch tự động: bạn vẫn phải chỉ định nhóm truy vấn nào phải được thực hiện trong một giao dịch.

Ví dụ, khá thường xuyên bạn sẽ có một vài thắc mắc trước khi giao dịch (trước begin) và một vài thắc mắc sau khi giao dịch (sau khi một trong hai commithoặc rollback) và bạn sẽ muốn những truy vấn thực hiện không có vấn đề gì xảy ra (hay không) trong sự giao dịch.


35
Hãy cẩn thận nếu bạn đang thực hiện các hoạt động có thể ném ngoại lệ khác với db. Nếu vậy, một ngoại lệ từ một câu lệnh không phải là db có thể vô tình gây ra rollback (ngay cả khi tất cả các cuộc gọi db đều thành công). Thông thường, bạn nghĩ rằng quay ngược lại là một ý tưởng hay ngay cả khi lỗi không thuộc về db, nhưng đôi khi mã bên thứ 3 / không quan trọng có thể gây ra các ngoại lệ không quan trọng và bạn vẫn muốn tiếp tục sự giao dịch.
Halil Özgür

6
Là gì $dbloại ở đây? mysqli?
Jake

3
@Jake Xem câu trả lời của tôi cho một ví dụ sử dụng mysqli (có phong cách tương tự như cách tiếp cận của Pascal).
EleventyOne 23/07/13

2
nó có thể dễ dàng được sửa đổi để bắt PDOExceptionvà thậm chí kiểm tra các giá trị ngoại lệ nếu cần. us2.php.net/PDOException
Yamiko

1
$ db là đối tượng PDO (kết nối). Tham chiếu: php.net/manual/en/pdo.connections.php
Fil

110

Tôi nghĩ rằng tôi đã tìm ra nó, phải không?:

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
không cần đặt autocommit = 0. giao dịch luôn hoạt động theo cách đó.
bgcode

2
@babonk - không chắc đây là trường hợp của InnoDB?
buggedcom

6
Tôi nghĩ rằng một khi bạn bắt đầu một giao dịch, nó hoạt động như thể
AUTOCOMMIT

4
@babonk nói đúng. Khi giao dịch được bắt đầu, AUTOCOMMIT = 0 đang được đặt hoàn toàn và sau khi giao dịch kết thúc bằng cam kết hoặc khôi phục, MySql sẽ đặt lại giá trị AUTOCOMMIT đã được sử dụng trước khi bắt đầu giao dịch. LƯU Ý: Bạn KHÔNG nên đặt AUTOCOMMIT = 0, vì sau khi thực hiện các thay đổi nếu bạn quyết định chèn / cập nhật một hàng khác, bạn nên cam kết rõ ràng.
eroteev

4
Cửa hàng động cơ phải là InnoDB, không phải 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";
}

?>

Đối với một câu hỏi hồ sơ rộng và cao như thế này, sẽ thật tuyệt nếu câu trả lời cũng phản ánh điều đó. Mẫu mã của bạn là tuyệt vời, nhưng bạn có thể xây dựng nhiều hơn? Giải thích về giao dịch, tại sao, khi nào và ở đâu? Cuối cùng, liên kết mã với lời giải thích của bạn.
Dennis Haarbrink

3
Chào mừng bạn đến với StackOverflow. Hãy luôn luôn viết một số văn bản mô tả câu trả lời của bạn.
Adrian Heine

6
xin lỗi tôi là người mới bắt đầu và tiếng anh không tốt của tôi chek them - tôi sử dụng mã này, điều này rất dễ hiểu =)
Gedzberg Alex

20
Nhận xét của ông làm cho ví dụ khá rõ ràng. Mã tốt không cần mô tả văn bản. Ngoài ra câu hỏi yêu cầu một ví dụ đơn giản. Tôi thích câu trả lời này.
Không có

@GedzbergAlex cho một truy vấn duy nhất không cần giao dịch, đơn giản là nó nhầm lẫn về giao dịch, Có lý do nào để sử dụng giao dịch cho một truy vấn không?
ʞɔIɥʇɹɐʞ ouɐɯ

35

Vì đây là kết quả đầu tiên trên google cho "giao dịch php mysql", tôi nghĩ rằng tôi đã thêm một câu trả lời thể hiện rõ ràng cách thực hiện điều này với mysqli (như ví dụ ban đầu của tác giả). Đây là một ví dụ đơn giản về các giao dịch với 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   
}

Ngoài ra, hãy nhớ rằng PHP 5.5 có một phương thức mới mysqli :: started_transaction . Tuy nhiên, điều này chưa được nhóm PHP ghi lại và tôi vẫn bị mắc kẹt trong PHP 5.3, vì vậy tôi không thể nhận xét về nó.


2
Về một lưu ý liên quan, tôi mới phát hiện ra rằng nếu bạn đang làm việc với các bảng InnoDB, có thể khóa / mở khóa các bảng khi sử dụng phương pháp autocomitt () cho các giao dịch, nhưng KHÔNG thể sử dụng phương pháp start_transaction (): MySQL tài liệu
EleventyOne

+1 cho ví dụ chi tiết (và nhận xét) với mã mysqli thực tế. Cảm ơn vì điều đó. Và quan điểm của bạn về khóa / giao dịch thực sự rất thú vị.
a.real.human. Kể từ

1
"Autocommit (FALSE)" có ảnh hưởng đến một kết nối khác trong cùng cơ sở dữ liệu / bảng không? Ý tôi là nếu chúng ta mở hai trang mà một trong số chúng đặt kết nối của nó thành "autocommit (FALSE)" nhưng một trang khác lại rời khỏi chức năng autocommit, nó có chờ chức năng cam kết hay không. Tôi muốn biết nếu autocommit là một thuộc tính cho các kết nối chứ không phải cho cơ sở dữ liệu / bảng. Cảm ơn
Hamid

2
@ Hamid $conn->autocommit(FALSE), trong ví dụ trên, chỉ ảnh hưởng đến kết nối cá nhân - nó không ảnh hưởng đến bất kỳ kết nối nào khác đến cơ sở dữ liệu.
EleventyOne

1
LƯU Ý: thay vì if (!result), nên làm if (result === false), nếu truy vấn có khả năng trả về kết quả hợp lệ sẽ đánh giá thành sai hoặc bằng không.
ToolmakerSteve

10

Vui lòng kiểm tra công cụ lưu trữ nào bạn đang sử dụng. Nếu đó là MyISAM, thì Transaction('COMMIT','ROLLBACK')sẽ không được hỗ trợ vì chỉ có công cụ lưu trữ InnoDB, không phải MyISAM, hỗ trợ các giao dịch.


7

Khi sử dụng kết nối PDO:

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

Tôi thường sử dụng mã sau đây để quản lý giao dịch:

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
    }
}

Ví dụ sử dụng:

transaction(function()
{
    global $pdo;

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

Bằng cách này, mã quản lý giao dịch không bị trùng lặp trong toàn dự án. Đó là một điều tốt, bởi vì, đánh giá từ các câu trả lời được phân loại theo PDO khác trong chủ đề này, thật dễ dàng để mắc lỗi trong đó. Những cái phổ biến nhất đang quên để lấy lại ngoại lệ và bắt đầu giao dịch bên trong trykhối.


5

Tôi đã thực hiện một chức năng để có được một vectơ truy vấn và thực hiện giao dịch, có thể ai đó sẽ tìm thấy nó hữu ích:

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

Tôi đã có điều này, nhưng không chắc chắn nếu điều này là chính xác. Cũng có thể thử điều này.

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");
}

Ý tưởng từ đây: http://www.phpknhow.com/mysql/transilities/


Không đúng mã. trigger_error sẽ trả về true (trừ khi bạn thực hiện cuộc gọi của mình), vì vậy $ result sẽ luôn đúng, vì vậy mã này sẽ bỏ lỡ mọi truy vấn không thành công và luôn cố gắng thực hiện. Khó khăn không kém, bạn đang sử dụng cái cũ không dùng nữa mysql_query, thay vì sử dụng mysqli, mặc dù bạn liên kết đến một hướng dẫn sử dụng mysqli. IMHO, bạn nên xóa ví dụ xấu này hoặc thay thế nó để sử dụng mã như được viết trong hướng dẫn phpknhow.
ToolmakerSteve

2

Thêm một ví dụ về phong cách thủ tục với mysqli_multi_query, các giả định $querychứa đầy các câu lệnh được phân tách bằng dấu chấm phẩy.

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
Một số mã lạ, nhưng ít nhất nó cho thấy việc sử dụng mysqli_multi_query. Bất cứ ai quan tâm đến điều đó nên google nơi khác cho ví dụ sạch hơn.
ToolmakerSteve
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.