วิธีการใช้ล็อคในแง่ดีอย่างถูกต้องใน MySQL


13

หนึ่งจะใช้ล็อคในแง่ดีอย่างถูกต้องใน MySQL ได้อย่างไร

ทีมของเราได้อนุมานว่าเราต้องทำ # 4 ด้านล่างมิฉะนั้นอาจมีความเสี่ยงที่เธรดอื่นสามารถอัปเดตบันทึกรุ่นเดียวกัน แต่เราต้องการตรวจสอบว่านี่เป็นวิธีที่ดีที่สุด

  1. สร้างเขตข้อมูลเวอร์ชันบนตารางที่คุณต้องการใช้การล็อคในแง่ดีสำหรับคอลัมน์ชื่อ = "รุ่น"
  2. เมื่อเลือกตรวจสอบให้แน่ใจว่าได้รวมคอลัมน์รุ่นแล้วจดบันทึกเวอร์ชัน
  3. ในการอัปเดตครั้งต่อ ๆ ไปคำสั่งการอัปเดตควรออก "โดยที่ version = X" โดยที่ X คือรุ่นที่เราได้รับใน # 2 และตั้งค่าฟิลด์เวอร์ชันระหว่างคำสั่งการอัปเดตนั้นเป็น X + 1
  4. ดำเนินการSELECT FOR UPDATEเกี่ยวกับบันทึกที่เรากำลังจะอัปเดตเพื่อให้เราเป็นอันดับที่สามารถเปลี่ยนแปลงบันทึกที่เรากำลังพยายามที่จะปรับปรุง

เพื่อชี้แจงเราพยายามป้องกันสองเธรดที่เลือกเร็กคอร์ดเดียวกันในหน้าต่างเวลาเดียวกันที่พวกเขาคว้าเร็กคอร์ดเวอร์ชันเดียวกันจากการเขียนทับกันถ้าพวกเขาพยายามและอัพเดตเร็กคอร์ดในเวลาเดียวกัน เราเชื่อว่าหากเราไม่ทำ # 4 มีโอกาสที่ถ้าทั้งสองเธรดป้อนธุรกรรมของตนในเวลาเดียวกัน (แต่ยังไม่ได้ออกการอัปเดต) เมื่อพวกเขาไปอัปเดตเธรดที่สองที่จะใช้ UPDATE ... โดยที่ version = X จะทำงานกับข้อมูลเก่า

เราคิดถูกต้องหรือไม่ว่าเราต้องทำการล็อกในแง่ร้ายเมื่อทำการอัพเดตแม้ว่าเราจะใช้ฟิลด์เวอร์ชั่น / การล็อคในแง่ดี


มีปัญหาอะไร? คุณเพิ่มหมายเลขรุ่นด้วย UPDATE ของคุณจากนั้นการอัปเดตครั้งที่สองจะล้มเหลวเนื่องจากหมายเลขเวอร์ชันไม่เหมือนกับเมื่ออ่าน - ซึ่งเป็นสิ่งที่คุณต้องการ
AndreKR

คุณแน่ใจหรือ มันไม่ชัดเจนว่าถ้าคุณตั้งค่าระดับการแยกธุรกรรมเป็นการตั้งค่าเฉพาะที่คุณจะเห็นการปรับปรุงเธรดอื่น ๆ จริง ๆ ถ้าคุณทั้งสองป้อนธุรกรรมในเวลาเดียวกันเธรดที่สองสามารถดูข้อมูล OLD ได้ดีเมื่อทำการอัปเดต MySQL นั้นมีความแข็งแกร่งในระดับที่ไม่มากนักเช่นเดียวกับ Oracle ดังนั้นจึงมองหาวิธีปฏิบัติที่ดีที่สุดในการใช้การล็อกในแง่ดีใน MySQL ซึ่งจะป้องกันการอ่าน / อัพเดทที่สกปรก
BestPractices

แต่การทำธุรกรรมจะล้มเหลวในระหว่างการกระทำใช่มั้ย
AndreKR

สิ่งบ่งชี้คือเราต้องการเลือกตัวเลือกสำหรับการอัปเดตเพื่อจัดการกับสถานการณ์นี้: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices

@BestPractices คุณต้องล็อคอย่างใดอย่างหนึ่ง SELECT ... FOR UPDATEหรือในแง่ดีโดยการกำหนดเวอร์ชันของแถวไม่ใช่ทั้งสองอย่าง ดูรายละเอียดในคำตอบ
Craig Ringer

คำตอบ:


17

นักพัฒนาของคุณเข้าใจผิด คุณจำเป็นต้องมีอย่างใดอย่างหนึ่ง SELECT ... FOR UPDATE หรือแถวเวอร์ชันไม่ใช่ทั้งสอง

ลองและดู เปิดสามช่วง MySQL (A), (B)และ(C)ไปยังฐานข้อมูลเดียวกัน

ใน(C)ประเด็น:

CREATE TABLE test(
    id integer PRIMARY KEY,
    data varchar(255) not null,
    version integer not null
);
INSERT INTO test(id,data,version) VALUES (1,'fred',0);
BEGIN;
LOCK TABLES test WRITE;

ทั้งใน(A)และ(B)ออกการUPDATEทดสอบและการตั้งค่ารุ่นแถวการเปลี่ยนwinnerข้อความในแต่ละเพื่อให้คุณสามารถดูว่าเซสชันใดที่:

-- In (A):

BEGIN;
UPDATE test SET data = 'winnerA',
            version = version + 1
WHERE id = 1 AND version = 0;

-- in (B):

BEGIN;
UPDATE test SET data = 'winnerB',
            version = version + 1
WHERE id = 1 AND version = 0;

ตอนนี้ใน(C), UNLOCK TABLES;การปลดล็อค

(A)และ(B)จะแข่งเพื่อล็อคแถว หนึ่งในนั้นจะชนะและได้รับการล็อค อีกอันจะบล็อคที่ล็อค ผู้ชนะที่ได้รับการล็อคจะดำเนินการเปลี่ยนแถว สมมติว่า(A)เป็นผู้ชนะตอนนี้คุณสามารถเห็นแถวที่มีการเปลี่ยนแปลง (ยังคงปราศจากข้อผูกมัดจึงไม่สามารถมองเห็นการทำธุรกรรมอื่น ๆ ) SELECT * FROM test WHERE id = 1ด้วย

ตอนนี้ในเซสชั่นผู้ชนะกล่าวว่าCOMMIT(A)

(B)จะได้รับการล็อคและดำเนินการอัปเดตต่อไป อย่างไรก็ตามเวอร์ชันจะไม่ตรงกันอีกต่อไปดังนั้นจะไม่มีการเปลี่ยนแปลงแถวดังที่รายงานโดยผลการนับแถว มีเพียงรายการเดียวที่UPDATEมีผลกระทบใด ๆ และแอปพลิเคชันไคลเอนต์สามารถดูได้อย่างชัดเจนว่าUPDATEประสบความสำเร็จและล้มเหลวใด ไม่จำเป็นต้องล็อคเพิ่มเติม

ดูบันทึกเซสชั่นที่ Pastebin ที่นี่ ฉันใช้mysql --prompt="A> "ฯลฯ เพื่อให้ง่ายต่อการบอกความแตกต่างระหว่างเซสชัน ฉันคัดลอกและวางเอาท์พุท interleaved ตามลำดับเวลาดังนั้นมันจึงไม่ได้เป็นข้อมูลดิบทั้งหมดและเป็นไปได้ที่ฉันสามารถทำข้อผิดพลาดในการคัดลอกและวางได้ ทดสอบด้วยตัวเองเพื่อดู


ถ้าคุณได้ไม่ได้เพิ่มข้อมูลรุ่นแถวแล้วคุณจะต้องSELECT ... FOR UPDATEเพื่อให้สามารถได้อย่างน่าเชื่อถือให้แน่ใจว่าการสั่งซื้อ

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

จุดประสงค์ของการSELECT ... FOR UPDATEคือ:

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

คุณไม่จำเป็นต้องใช้ทั้งการล็อคในแง่ดี (แถวเวอร์ชัน) SELECT ... FOR UPDATEและ ใช้อย่างใดอย่างหนึ่ง


ขอบคุณเครก คุณถูกต้อง - ผู้พัฒนาเข้าใจผิด ขอบคุณที่ใช้การทดสอบนี้
BestPractices

แล้วเซิร์ฟเวอร์ SQL ล่ะ มีการล็อคที่ได้รับในแถวที่อัพเดตเสมอโดยไม่ขึ้นกับระดับการแยกธุรกรรมหรือไม่?
plalx

@plalx เอกสารนี้พูดว่าอะไรดี? จะเกิดอะไรขึ้นถ้าคุณเรียกใช้การทดสอบเชิงโต้ตอบเหมือนกับแบบนี้
Craig Ringer

@CraigRinger จะเกิดอะไรขึ้นถ้า B ได้รับการล็อคก่อน A commit แต่หลังจากอัพเดทแล้ว?
MengT

1
@MengT ไม่ได้นั่นเป็นเหตุผลว่าทำไมมันถึงเป็นล็อค
Craig Ringer

0
UPDATE tbl SET owner = $me,
               id = LAST_INSERT_ID(id)
    WHERE owner = ''
    LIMIT 1;
$id = SELECT LAST_INSERT_ID();
Do some stuff (arbitrarily long time)...;
UPDATE  tbl SET owner = '' WHERE id = $id;

ไม่ต้องล็อค (ไม่ใช่ตารางไม่ใช่ธุรกรรม) หรือแม้แต่ต้องการ:

  • การปรับปรุงคืออะตอม
  • LAST_INSERT_ID () เป็นเฉพาะเซสชันดังนั้นจึงปลอดภัยต่อเธรด
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.