ตัวอย่างธุรกรรม PHP + MySQL


294

ฉันไม่ได้พบตัวอย่างปกติของไฟล์ PHP ที่ใช้งานธุรกรรม MySQL อยู่ คุณช่วยแสดงตัวอย่างง่ายๆให้ฉันฟังได้ไหม

และอีกหนึ่งคำถาม ฉันได้ทำการเขียนโปรแกรมมามากมายและไม่ได้ใช้ธุรกรรม ฉันสามารถใส่ฟังก์ชั่นของ 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

75
กรุณาอย่าใช้mysql_*ฟังก์ชั่นในรหัสใหม่ พวกเขาจะไม่เก็บรักษาไว้และมีการยกเลิกอย่างเป็นทางการ เห็นกล่องสีแดง ? เรียนรู้เกี่ยวกับงบที่เตรียมไว้และใช้ PDOหรือ MySQLi -บทความนี้จะช่วยคุณตัดสินใจว่า หากคุณเลือกที่ PDO,นี่คือการสอนที่ดี
Naftali aka Neal

6
ใช้ "mysql_query (" SET AUTOCOMMIT = 0 ") หรือไม่" ตั้งค่าการเชื่อมต่อทั้งหมดเพื่อรอฟังก์ชั่นการคอมมิชชันหรือเป็นเพียงการเชื่อมต่อที่เกี่ยวข้อง
ฮามิด

1
@ Nal, อันที่จริงmysqlwun ตายแม้จะถูกคัดค้าน, มันจะอยู่ใน PECL ตลอดไป
Pacerier

2
@Pierier สิ่งที่เลิกใช้อย่า "ตาย" พวกเขาจะจัดขึ้นอย่างเป็นทางการสำหรับซอฟต์แวร์มรดก แต่จะหยุดและรักษาจากการปฏิบัติที่แนะนำสำหรับซอฟต์แวร์ใหม่ ความจริงยังคงอยู่อย่าใช้mysql
taylorcressy

คำตอบ:


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


โปรดทราบว่าด้วยความคิดนี้หากแบบสอบถามล้มเหลวจะต้องมีข้อยกเว้น:

  • PDO สามารถทำได้ขึ้นอยู่กับว่าคุณกำหนดค่าไว้อย่างไร
  • อื่นด้วย API อื่นคุณอาจต้องทดสอบผลลัพธ์ของฟังก์ชันที่ใช้ในการดำเนินการค้นหาและส่งข้อยกเว้นด้วยตัวคุณเอง


น่าเสียดายที่ไม่มีเวทมนตร์เข้ามาเกี่ยวข้อง คุณไม่สามารถใส่คำสั่งที่ไหนสักแห่งและทำธุรกรรมโดยอัตโนมัติ: คุณยังต้องระบุกลุ่มของแบบสอบถามที่จะต้องดำเนินการในการทำธุรกรรม

ยกตัวอย่างเช่นค่อนข้างบ่อยคุณจะมีคู่ของคำสั่งก่อนการทำธุรกรรม(ก่อนbegin)และคู่อื่นของแบบสอบถามหลังการทำรายการ(หลังจากทั้งสองcommitหรือrollback)และคุณจะต้องการคำสั่งเหล่านั้นดำเนินการเรื่องสิ่งที่เกิดขึ้นไม่มี(หรือไม่)ใน การทำธุรกรรม


35
ระวังหากคุณกำลังดำเนินการที่อาจส่งข้อยกเว้นอื่นนอกเหนือจากฐานข้อมูล หากเป็นเช่นนั้นข้อยกเว้นจากคำสั่งที่ไม่ใช่ db อาจทำให้เกิดการย้อนกลับโดยไม่ตั้งใจ (แม้ว่าการเรียก db ทั้งหมดจะสำเร็จ) โดยปกติคุณจะคิดว่าการย้อนกลับเป็นความคิดที่ดีแม้ว่าข้อผิดพลาดจะไม่อยู่ในฝั่ง db แต่มีบางครั้งที่รหัสบุคคลที่สาม / ไม่สำคัญอาจทำให้เกิดข้อยกเว้นที่ไม่สำคัญและคุณยังต้องการดำเนินการต่อ การทำธุรกรรม
Halil Özgür

6
อะไรคือสิ่งที่$dbประเภทที่นี่? mysqli?
Jake

3
@ Jake ดูคำตอบของฉันสำหรับตัวอย่างที่ใช้ mysqli (คล้ายกับแนวทางของ Pascal)
EleventyOne

2
สามารถแก้ไขได้อย่างง่ายดายเพื่อตรวจจับPDOExceptionและตรวจสอบค่าข้อยกเว้นหากจำเป็น us2.php.net/PDOException
Yamiko

1
$ dbเป็นวัตถุ PDO (การเชื่อมต่อ) Ref: 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

6
ฉันคิดว่าเมื่อคุณเริ่มทำธุรกรรมมันจะทำงานเหมือนกับว่า AUTOCOMMIT = 0
bgcode

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

?>

สำหรับคำถามที่ละเอียดและกว้างเช่นนี้มันจะดีถ้าคำตอบนั้นสะท้อนเช่นกัน ตัวอย่างโค้ดของคุณยอดเยี่ยม แต่คุณสามารถอธิบายเพิ่มเติมได้หรือไม่ อธิบายเกี่ยวกับธุรกรรมทำไมเมื่อไหร่และที่ไหน สุดท้ายเชื่อมโยงรหัสกับคำอธิบายของคุณ
Dennis Haarbrink

3
ยินดีต้อนรับสู่ StackOverflow โปรดเขียนข้อความอธิบายลงในคำตอบของคุณเสมอ
Adrian Heine

6
ขออภัย im begginer และภาษาอังกฤษที่ไม่ดีของฉันมันง่ายมากในการตรวจสอบโค้ด - สำหรับผู้เริ่มต้น - กระทำ () ย้อนกลับ () เริ่มต้น () ใส่ในคลาส DB (ตัวอย่าง), $ query - ไม่ใช่ครั้งเดียว - อาจเป็น $ query0 $ query1 - จากนั้น chek พวกเขา - ฉันใช้รหัสนี้ง่ายมากที่จะเข้าใจ =)
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 () ในการทำธุรกรรม แต่มันเป็นไปไม่ได้เมื่อใช้วิธี start_transaction (): MySQL เอกสารประกอบ
EleventyOne

+1 สำหรับรายละเอียด (และแสดงความคิดเห็น) ตัวอย่างพร้อมรหัส mysqli จริง ขอบคุณสำหรับสิ่งนี้. และประเด็นของคุณเกี่ยวกับการล็อค / ธุรกรรมนั้นน่าสนใจมาก
a.real.human.being

1
"autocommit (FALSE)" จะส่งผลต่อการเชื่อมต่ออื่นในฐานข้อมูล / ตารางเดียวกันหรือไม่ ฉันหมายถึงถ้าเราเปิดสองหน้าซึ่งหนึ่งในนั้นตั้งค่าการเชื่อมต่อเป็น "autocommit (FALSE)" แต่อีกหน้าหนึ่งออกจากฟังก์ชัน autocommit มันจะรอฟังก์ชั่นการคอมมิชชันหรือไม่ ฉันต้องการทราบว่า autocommit เป็นคุณสมบัติสำหรับการเชื่อมต่อและไม่ใช่สำหรับฐานข้อมูล / ตาราง ขอบคุณ
ฮามิด

2
@Hidid $conn->autocommit(FALSE)ในตัวอย่างด้านบนมีผลกับการเชื่อมต่อส่วนบุคคลเท่านั้น - ไม่มีผลต่อการเชื่อมต่ออื่น ๆ กับฐานข้อมูล
EleventyOne

1
หมายเหตุ: แทนที่จะ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 จะคืนค่าเป็นจริง (เว้นแต่ว่าคุณได้ทำให้การโทรของคุณแย่ลง) ดังนั้นผลลัพธ์ของ $ จะเป็นจริงเสมอดังนั้นรหัสนี้จะพลาดการสืบค้นที่ล้มเหลวและพยายามส่ง คุณกำลังใช้สิ่งที่คัดค้านเก่า ๆmysql_queryแทนที่จะใช้mysqliแม้ว่าคุณจะลิงก์ไปยังบทช่วยสอนที่ใช้mysqliก็ตาม IMHO คุณควรลบตัวอย่างที่ไม่ดีนี้หรือแทนที่ด้วยรหัสที่เขียนในบทช่วยสอน 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แต่อย่างน้อยก็แสดงให้เห็นการใช้งานของ ทุกคนที่สนใจในสิ่งนี้ควรเป็นที่อื่นในการทำความสะอาด
ToolmakerSteve
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.