MySQL: ธุรกรรมเทียบกับตารางการล็อก


110

ฉันสับสนเล็กน้อยกับการทำธุรกรรมและการล็อกตารางเพื่อให้แน่ใจว่าฐานข้อมูลมีความสมบูรณ์และตรวจสอบให้แน่ใจว่า SELECT และ UPDATE ยังคงซิงค์อยู่และไม่มีการเชื่อมต่ออื่นใดรบกวน ฉันจำเป็นต้อง:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

ฉันต้องตรวจสอบให้แน่ใจว่าไม่มีการสอบถามอื่นใดที่จะรบกวนและดำเนินการแบบเดียวกันSELECT(อ่าน 'ค่าเก่า' ก่อนที่การเชื่อมต่อนั้นจะอัปเดตแถวเสร็จสิ้น

ฉันรู้ว่าฉันสามารถตั้งค่าเริ่มต้นเพื่อLOCK TABLES tableตรวจสอบให้แน่ใจว่ามีการเชื่อมต่อเพียงครั้งละ 1 รายการเท่านั้นและปลดล็อกเมื่อฉันทำเสร็จแล้ว แต่ดูเหมือนว่าจะมากเกินไป การตัดสิ่งนั้นในการทำธุรกรรมจะทำสิ่งเดียวกันหรือไม่ (ตรวจสอบให้แน่ใจว่าไม่มีการเชื่อมต่ออื่นใดพยายามทำกระบวนการเดียวกันในขณะที่อีกรายการหนึ่งกำลังประมวลผล) หรือจะเป็นSELECT ... FOR UPDATEหรือSELECT ... LOCK IN SHARE MODEดีกว่า?

คำตอบ:


173

การล็อกตารางจะป้องกันไม่ให้ผู้ใช้ DB รายอื่นส่งผลกระทบต่อแถว / ตารางที่คุณล็อก แต่การล็อกในตัวมันเองจะไม่ทำให้แน่ใจว่าตรรกะของคุณออกมาอยู่ในสถานะที่สอดคล้องกัน

ลองนึกถึงระบบธนาคาร เมื่อคุณจ่ายบิลออนไลน์มีบัญชีอย่างน้อยสองบัญชีที่ได้รับผลกระทบจากธุรกรรม: บัญชีของคุณซึ่งเป็นเงินที่ถูกนำไป และบัญชีของผู้รับเงินที่โอนเข้ามา และบัญชีของธนาคารซึ่งพวกเขาจะฝากเงินค่าบริการทั้งหมดที่เรียกเก็บจากธุรกรรมอย่างมีความสุข ระบุ (อย่างที่ทุกคนรู้ในทุกวันนี้) ว่าธนาคารโง่เป็นพิเศษสมมติว่าระบบของพวกเขาทำงานเช่นนี้:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

ขณะนี้ระบบนี้ไม่มีการล็อกและไม่มีธุรกรรมใด ๆ ระบบนี้มีความเสี่ยงต่อสภาวะการแข่งขันที่หลากหลายซึ่งใหญ่ที่สุดคือการชำระเงินหลายรายการในบัญชีของคุณหรือบัญชีของผู้รับพร้อมกัน ในขณะที่รหัสของคุณได้รับยอดเงินของคุณถูกเรียกคืนและกำลังทำขนาดใหญ่ _overdraft_fees () แต่ก็เป็นไปได้ทั้งหมดที่การชำระเงินอื่น ๆ บางส่วนจะใช้รหัสประเภทเดียวกันควบคู่กันไป พวกเขาจะเรียกคืนยอดคงเหลือของคุณ (เช่น $ 100) ทำธุรกรรมของพวกเขา (นำเงิน 20 ดอลลาร์ที่คุณจ่ายไปและ 30 ดอลลาร์ที่พวกเขากำลังทำให้คุณเสียหาย) และตอนนี้เส้นทางรหัสทั้งสองมียอดคงเหลือสองแบบ: $ 80 และ 70 เหรียญ ขึ้นอยู่กับว่ารายการใดเสร็จสิ้นสุดท้ายคุณจะได้รับยอดคงเหลือสองรายการในบัญชีของคุณแทนที่จะเป็น $ 50 ที่คุณควรได้รับ ($ 100 - $ 20 - $ 30) ในกรณีนี้ "ข้อผิดพลาดของธนาคารที่คุณโปรดปราน"

ตอนนี้สมมติว่าคุณใช้ล็อค การจ่ายบิล ($ 20) ของคุณเข้าสู่ท่อก่อนดังนั้นจึงชนะและล็อกบันทึกบัญชีของคุณ ตอนนี้คุณมีการใช้งานพิเศษและสามารถหักเงิน 20 ดอลลาร์จากยอดคงเหลือและเขียนยอดเงินใหม่กลับมาอย่างสงบ ... และบัญชีของคุณจะจบลงด้วย $ 80 ตามที่คาดไว้ แต่ ... เอ่อ ... คุณลองไปอัปเดตบัญชีของผู้รับแล้วมันถูกล็อคและล็อคนานเกินกว่าที่รหัสจะอนุญาตทำให้หมดเวลาการทำธุรกรรมของคุณ ... เรากำลังติดต่อกับธนาคารโง่ ๆ ดังนั้นแทนที่จะมีข้อผิดพลาดที่เหมาะสม การจัดการรหัสเพียงแค่ดึงexit()และ $ 20 ของคุณหายไปในพัฟของอิเล็กตรอน ตอนนี้คุณออกเงิน 20 เหรียญและคุณยังคงเป็นหนี้ผู้รับ 20 เหรียญและโทรศัพท์ของคุณก็ถูกยึดคืน

ดังนั้น ... เข้าสู่การทำธุรกรรม คุณเริ่มต้นธุรกรรมคุณหักบัญชีของคุณ $ 20 คุณพยายามให้เครดิตผู้รับด้วย $ 20 ... และมีบางอย่างเกิดขึ้นอีกครั้ง แต่คราวนี้แทนที่จะexit()เป็นโค้ดก็ทำได้rollbackและเงิน $ 20 ของคุณจะถูกเพิ่มกลับเข้าไปในบัญชีของคุณอย่างน่าอัศจรรย์

ในท้ายที่สุดมันก็เดือดลงถึงสิ่งนี้:

การล็อกป้องกันไม่ให้บุคคลอื่นเข้าไปยุ่งเกี่ยวกับบันทึกฐานข้อมูลใด ๆ ที่คุณกำลังติดต่อด้วย ธุรกรรมจะป้องกันไม่ให้ข้อผิดพลาด "ในภายหลัง" รบกวนสิ่งที่ "ก่อนหน้านี้" ที่คุณทำ ไม่มีใครสามารถรับประกันได้ว่าสิ่งต่างๆจะออกมาดีในที่สุด แต่ร่วมกันทำ

ในบทเรียนวันพรุ่งนี้: Joy of Deadlocks


4
ฉันยัง / ยังสับสน สมมติว่าบัญชีผู้รับมีเงิน $ 100 ในการเริ่มต้นและเรากำลังเพิ่มการชำระเงิน $ 20 จากบัญชีของเรา ความเข้าใจของฉันเกี่ยวกับธุรกรรมคือเมื่อเริ่มต้นการดำเนินการในธุรกรรมใด ๆ จะเห็นฐานข้อมูลในสถานะที่เป็นจุดเริ่มต้นของธุรกรรม เช่น: จนกว่าเราจะเปลี่ยนบัญชีผู้รับจะมีเงิน $ 100 ดังนั้น ... เมื่อเราเพิ่ม $ 20 เรากำหนดยอดคงเหลือ $ 120 แต่จะเกิดอะไรขึ้นถ้าในระหว่างการทำธุรกรรมของเรามีคนใช้เงินในบัญชีผู้รับเป็น $ 0? สิ่งนี้ได้รับการป้องกันหรือไม่? พวกเขาได้รับ $ 120 อีกครั้งอย่างน่าอัศจรรย์หรือไม่? นี่คือเหตุผลที่จำเป็นต้องมีการล็อคด้วยหรือไม่?
รัส

ใช่นั่นคือสิ่งที่ล็อคเข้ามามีบทบาท ระบบที่เหมาะสมจะเขียนล็อกบันทึกเพื่อไม่ให้ผู้อื่นสามารถอัปเดตบันทึกได้ในขณะที่ธุรกรรมกำลังดำเนินการอยู่ ระบบที่หวาดระแวงจะทำให้การบันทึกมีการล็อกโดยไม่มีเงื่อนไขดังนั้นจึงไม่มีใครสามารถอ่านยอดคงเหลือที่ "ค้าง" ได้เช่นกัน
Marc B

1
โดยทั่วไปมองว่าธุรกรรมเป็นการรักษาความปลอดภัยสิ่งต่างๆภายในเส้นทางรหัสของคุณ ล็อคสิ่งที่ปลอดภัยข้ามเส้นทางรหัส "ขนาน" จนกว่าจะมีการหยุดชะงัก ...
Marc B

1
@MarcB เหตุใดเราจึงต้องทำการล็อคอย่างชัดเจนหากใช้ธุรกรรมเพียงอย่างเดียวแล้วรับรองว่าล็อกเข้าที่แล้ว? จะมีกรณีหรือไม่ที่เราต้องทำการล็อกอย่างชัดเจนเนื่องจากการทำธุรกรรมเพียงอย่างเดียวไม่เพียงพอหรือไม่?
Pacerier

2
คำตอบนี้ไม่ถูกต้องและอาจนำไปสู่ข้อสรุปที่ผิด คำแถลงนี้: "ล็อกป้องกันไม่ให้บุคคลอื่นเข้าไปยุ่งเกี่ยวกับบันทึกฐานข้อมูลใด ๆ ที่คุณกำลังติดต่อธุรกรรมจะป้องกันข้อผิดพลาด" ในภายหลัง "จากการแทรกแซงสิ่งที่" ก่อนหน้านี้ "ที่คุณทำทั้งสองอย่างเดียวไม่สามารถรับประกันได้ว่าสิ่งต่างๆจะเป็นไปด้วยดีใน จบ. แต่ร่วมกันทำ” - จะทำให้คุณถูกไล่ออกมันผิดและโง่มากดูบทความ: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems)และdev.mysql.com/doc/refman/5.1/ th / …
Nikola Svitlica

14

คุณต้องการSELECT ... FOR UPDATEหรือSELECT ... LOCK IN SHARE MODEภายในธุรกรรมตามที่คุณกล่าวไว้เนื่องจากโดยปกติ SELECT ไม่ว่าจะอยู่ในธุรกรรมหรือไม่ก็ตามจะไม่ล็อกตาราง รายการใดที่คุณเลือกจะขึ้นอยู่กับว่าคุณต้องการให้ธุรกรรมอื่นสามารถอ่านแถวนั้นได้ในขณะที่ธุรกรรมของคุณกำลังดำเนินการอยู่

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTจะไม่หลอกลวงคุณเนื่องจากธุรกรรมอื่น ๆ ยังคงสามารถเข้ามาแก้ไขแถวนั้นได้ สิ่งนี้ถูกกล่าวถึงที่ด้านบนของลิงก์ด้านล่าง

หากเซสชันอื่นอัปเดตตารางเดียวกันพร้อมกัน [... ] คุณอาจเห็นตารางอยู่ในสถานะที่ไม่เคยมีอยู่ในฐานข้อมูล

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html


8

แนวคิดการทำธุรกรรมและการล็อกแตกต่างกัน อย่างไรก็ตามธุรกรรมใช้การล็อกเพื่อช่วยให้เป็นไปตามหลักการของกรด หากคุณต้องการไปที่โต๊ะเพื่อป้องกันไม่ให้ผู้อื่นอ่าน / เขียนในเวลาเดียวกันในขณะที่คุณอ่าน / เขียนคุณต้องมีตัวล็อกเพื่อทำสิ่งนี้ หากคุณต้องการให้แน่ใจว่าข้อมูลมีความสมบูรณ์และสอดคล้องกันคุณมีธุรกรรมที่ดีกว่า ฉันคิดว่าแนวคิดแบบผสมของระดับการแยกในธุรกรรมที่มีการล็อก โปรดค้นหาระดับการแยกธุรกรรม SERIALIZE ควรเป็นระดับที่คุณต้องการ


นี่น่าจะเป็นคำตอบที่ถูกต้อง การล็อกมีไว้เพื่อป้องกันสภาวะการแข่งขันและธุรกรรมมีไว้สำหรับการอัปเดตหลายตารางด้วยข้อมูลที่อ้างอิง สองแนวคิดที่แตกต่างกันโดยสิ้นเชิงแม้ว่าธุรกรรมนั้นจะใช้การล็อก
Blue Water

6

ฉันมีปัญหาที่คล้ายกันเมื่อพยายามIF NOT EXISTS ...และดำเนินการINSERTซึ่งทำให้เกิดสภาวะการแย่งชิงเมื่อหลายเธรดกำลังอัปเดตตารางเดียวกัน

ฉันพบวิธีแก้ปัญหาที่นี่: วิธีการเขียนแบบสอบถาม INSERT IF NOT EXISTS ใน SQL มาตรฐาน

ฉันตระหนักดีว่านี่ไม่ได้ตอบคำถามของคุณโดยตรง แต่หลักการเดียวกันในการตรวจสอบและแทรกเป็นคำสั่งเดียวนั้นมีประโยชน์มาก คุณควรจะแก้ไขได้เพื่อทำการอัปเดตของคุณ


2

คุณสับสนกับการล็อกและธุรกรรม เป็นสองสิ่งที่แตกต่างกันใน RMDB ล็อคป้องกันการดำเนินการพร้อมกันในขณะที่ธุรกรรมมุ่งเน้นไปที่การแยกข้อมูล ตรวจสอบนี้บทความที่ดีสำหรับการชี้แจงและแก้ปัญหาบางสง่างาม


1
การล็อกป้องกันไม่ให้ผู้อื่นแทรกแซงบันทึกที่คุณกำลังทำงานด้วยอธิบายสิ่งที่ทำอย่างรวบรัดและการทำธุรกรรมจะป้องกันข้อผิดพลาดในภายหลัง (ที่ผู้อื่นทำการเปลี่ยนแปลงควบคู่กัน) จากการแทรกแซงสิ่งที่คุณทำก่อนหน้านี้ (โดยอนุญาตให้ย้อนกลับในกรณีที่มีคนทำบางอย่าง ในแบบคู่ขนาน) สรุปธุรกรรมได้ค่อนข้างมาก ... อะไรคือความสับสนเกี่ยวกับความเข้าใจในหัวข้อเหล่านี้?
steviesama

1

ฉันจะใช้ไฟล์

START TRANSACTION WITH CONSISTENT SNAPSHOT;

เริ่มต้นด้วยและ

COMMIT;

ลงท้ายด้วย.

สิ่งที่คุณทำระหว่างนั้นจะแยกออกจากผู้ใช้อื่น ๆ ในฐานข้อมูลของคุณหากเอ็นจิ้นการจัดเก็บของคุณรองรับธุรกรรม (ซึ่งก็คือ InnoDB)


1
ยกเว้นตารางที่เขาเลือกจะไม่ถูกล็อกไปยังเซสชันอื่นเว้นแต่เขาจะล็อกไว้โดยเฉพาะ (หรือจนกว่าจะมีการอัปเดตของเขา) ซึ่งหมายความว่าเซสชันอื่น ๆ อาจเข้ามาแก้ไขระหว่างการเลือกและการอัปเดต
Alison R.

หลังจากอ่านข้อมูลเกี่ยวกับการเริ่มต้นการทำธุรกรรมด้วย SNAPSHOT ที่สอดคล้องกันในเอกสาร MySQL ฉันไม่เห็นว่ามันล็อกการเชื่อมต่ออื่นไม่ให้อัปเดตแถวเดียวกัน ความเข้าใจของฉันคือมันจะเห็นอย่างไรก็ตามตารางเริ่มต้นที่จุดเริ่มต้นของธุรกรรม ดังนั้นหากธุรกรรมอื่นอยู่ระหว่างดำเนินการมีแถวแล้วและกำลังจะอัปเดตธุรกรรมที่ 2 จะยังคงเห็นแถวก่อนที่จะมีการอัปเดต อาจเป็นไปได้ว่าอาจลองอัปเดตแถวเดียวกันกับที่ธุรกรรมอื่นกำลังจะทำ ถูกต้องหรือฉันพลาดอะไรบางอย่างในความคืบหน้า?
Ryan

1
@Ryan มันไม่ได้ทำการล็อคใด ๆ ; คุณถูก. การล็อก (หรือไม่) ถูกกำหนดโดยประเภทของการดำเนินการที่คุณทำ (SELECT / UPDATE / DELETE)
Alison R.

4
ฉันเห็น. ให้ความสอดคล้องในการอ่านธุรกรรมของคุณเอง แต่ไม่ได้บล็อกผู้ใช้รายอื่นจากการแก้ไขแถวก่อนที่คุณจะทำ
Martin Schapendonk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.