วิธีการหลีกเลี่ยงการหยุดชะงักของ mysql 'เมื่อพยายามล็อค; ลองเริ่มธุรกรรมใหม่ '


286

ฉันมีตาราง innoDB ซึ่งบันทึกผู้ใช้ออนไลน์ มันได้รับการปรับปรุงในทุกหน้ารีเฟรชโดยผู้ใช้เพื่อติดตามว่าพวกเขาอยู่ในหน้าและวันที่เข้าถึงล่าสุดไปยังเว็บไซต์ ฉันมี cron ที่ทำงานทุก ๆ 15 นาทีเพื่อลบระเบียนเก่า

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

=== แก้ไข ===

นี่คือแบบสอบถามที่กำลังทำงานอยู่:

เข้าชมไซต์ครั้งแรก:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

ในการรีเฟรชแต่ละหน้า:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

Cron ทุก ๆ 15 นาที:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

จากนั้นจะนับการบันทึกสถิติบางอย่าง (เช่นสมาชิกออนไลน์ผู้เยี่ยมชมออนไลน์)


คุณสามารถให้รายละเอียดเพิ่มเติมเกี่ยวกับโครงสร้างของตารางได้ไหม มีดัชนีแบบคลัสเตอร์หรือแบบไม่รวมกลุ่มหรือไม่?
Anders Abel

13
dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - การเรียกใช้ "แสดงสถานะเครื่องยนต์ Innodb" จะให้การวินิจฉัยที่มีประโยชน์
Martin

ไม่ใช่วิธีที่ดีที่จะเขียนฐานข้อมูลแบบซิงโครนัสเมื่อผู้ใช้ทำตามขั้นตอนบนหน้า วิธีที่ถูกต้องคือเก็บไว้ในหน่วยความจำเช่น memcache หรือคิวรวดเร็วและเขียนไปยัง db ด้วย cron
Nir

คำตอบ:


292

เคล็ดลับง่ายๆที่สามารถช่วยในการหยุดชะงักส่วนใหญ่คือการเรียงลำดับการดำเนินการในลำดับที่เฉพาะเจาะจง

คุณได้รับ deadlock เมื่อธุรกรรมสองรายการพยายามล็อคสองตัวที่คำสั่งตรงกันข้าม:

  • การเชื่อมต่อ 1: ล็อคกุญแจ (1) ล็อคกุญแจ (2);
  • การเชื่อมต่อ 2: ล็อคกุญแจ (2) ล็อคกุญแจ (1);

หากทั้งสองทำงานพร้อมกันการเชื่อมต่อ 1 จะล็อคกุญแจ (1) การเชื่อมต่อ 2 จะล็อคกุญแจ (2) และการเชื่อมต่อแต่ละครั้งจะรอให้อีกคนหนึ่งปล่อยกุญแจ -> การหยุดชะงัก

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

  • การเชื่อมต่อ 1: ล็อคกุญแจ (1) ล็อคกุญแจ (2);
  • การเชื่อมต่อ 2: ล็อคกุญแจ ( 1 ) ล็อคกุญแจ ( 2 );

มันจะเป็นไปไม่ได้ที่จะหยุดชะงัก

ดังนั้นนี่คือสิ่งที่ฉันแนะนำ:

  1. ตรวจสอบให้แน่ใจว่าคุณไม่มีคิวรีอื่นที่ล็อคการเข้าถึงมากกว่าหนึ่งคีย์ในแต่ละครั้งยกเว้นคำสั่งลบ ถ้าคุณทำ (และฉันสงสัยว่าคุณทำ) สั่งของพวกเขาที่ไหนใน (k1, k2, .. kn) ในลำดับจากน้อยไปมาก

  2. แก้ไขคำสั่งการลบของคุณเพื่อให้ทำงานตามลำดับ:

เปลี่ยนแปลง

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

ถึง

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

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


2
ถ้าฉันมีทรานแซคชัน (autocommit = false) ข้อยกเว้นเดดล็อกเกิดขึ้น เพียงพอหรือไม่ที่จะลองใช้ statement.executeUpdate เดียวกันอีกครั้งหรือธุรกรรมทั้งหมดได้ถูก gimped และควรย้อนกลับ + รันใหม่อีกครั้งทุกอย่างที่ทำงานอยู่ในนั้น
Whome

5
หากคุณเปิดใช้งานธุรกรรมทั้งหมดหรือไม่ก็ทั้งหมด หากคุณมีข้อยกเว้นใด ๆ ก็รับประกันได้ว่าธุรกรรมทั้งหมดไม่มีผล ในกรณีที่คุณต้องการเริ่มต้นใหม่ทั้งหมด
Omry Yadan

4
การลบที่อิงกับการเลือกบนโต๊ะขนาดใหญ่นั้นช้ากว่าการลบแบบธรรมดามาก
Thermech

3
ขอบคุณมากนะเพื่อน เคล็ดลับ 'ข้อความสั่งการจัดเรียง' แก้ไขปัญหาการล็อกที่ตายแล้วของฉัน
Miere

4
@OmryYadan จากสิ่งที่ฉันรู้ใน MySQL คุณไม่สามารถเลือกในแบบสอบถามย่อยจากตารางเดียวกันที่คุณกำลังปรับปรุง dev.mysql.com/doc/refman/5.7/en/update.html
artaxerxe

73

การหยุดชะงักเกิดขึ้นเมื่อสองธุรกรรมรอกันเพื่อรับการล็อก ตัวอย่าง:

  • Tx 1: ล็อค A จากนั้นกด B
  • Tx 2: ล็อก B จากนั้น A

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

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


3
ดังนั้นบางทีปัญหาของฉันคือผู้ใช้รีเฟรชหน้าเว็บและเรียกการอัปเดตของเรกคอร์ดในเวลาเดียวกัน cron พยายามเรียกใช้การลบบนเรกคอร์ด อย่างไรก็ตามฉันได้รับข้อผิดพลาดในการแทรกดังนั้น cron จะไม่ลบระเบียนที่เพิ่งสร้างขึ้น ดังนั้นการหยุดชะงักสามารถเกิดขึ้นกับบันทึกที่ยังไม่ถูกแทรกได้อย่างไร
เดวิด

คุณสามารถให้ข้อมูลเพิ่มเติมเล็กน้อยเกี่ยวกับตารางและธุรกรรมได้อย่างไร?
ewernli

ฉันไม่เห็นว่าการหยุดชะงักสามารถเกิดขึ้นได้อย่างไรหากมีเพียงหนึ่งคำสั่งต่อหนึ่งธุรกรรม ไม่มีการดำเนินการอื่น ๆ ในตารางอื่น ๆ ? ไม่มีกุญแจต่างประเทศพิเศษหรือข้อ จำกัด ที่ไม่ซ้ำกันหรือไม่ ไม่มีข้อ จำกัด ในการลบทั้งหมด
ewernli

ไม่เลยไม่มีอะไรพิเศษ ... ฉันคิดว่ามันเป็นธรรมชาติของการใช้โต๊ะ แถวจะถูกแทรก / อัปเดตทุกหน้ารีเฟรชจากผู้เข้าชม มีผู้เยี่ยมชมประมาณ 1,000 คนขึ้นไปในแต่ละครั้ง
David

12

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

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


6

คุณอาจลองให้deleteงานนั้นดำเนินการโดยการใส่รหัสของแต่ละแถวก่อนที่จะลบลงในตาราง temp เช่น pseudocode นี้

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

ทำลายมันขึ้นมาเช่นนี้จะมีประสิทธิภาพน้อยลง deleteแต่ก็หลีกเลี่ยงความจำเป็นที่จะถือกุญแจล็อคช่วงช่วง

นอกจากนี้ปรับเปลี่ยนselectแบบสอบถามของคุณเพื่อเพิ่มwhereข้อยกเว้นแถวที่เก่ากว่า 900 วินาที สิ่งนี้หลีกเลี่ยงการพึ่งพางาน cron และช่วยให้คุณจัดกำหนดการใหม่ให้ทำงานน้อยลง

ทฤษฎีเกี่ยวกับการหยุดชะงัก: ฉันไม่มีพื้นหลังจำนวนมากใน MySQL แต่ที่นี่ไป ... ที่deleteกำลังจะมีการล็อคช่วงคีย์สำหรับวันที่และเวลาเพื่อป้องกันไม่ให้แถวที่ตรงกับwhereการเพิ่มข้อในช่วงกลางของการทำธุรกรรม และเมื่อพบแถวที่จะลบมันจะพยายามรับการล็อคในแต่ละหน้ามันกำลังแก้ไข insertเป็นไปที่จะได้รับล็อคบนหน้าเว็บก็จะถูกใส่ลงในและจากนั้นพยายามที่จะได้รับกุญแจล็อค โดยปกติแล้วinsertจะรออย่างอดทนเพื่อให้การล็อคกุญแจนั้นเปิดขึ้น แต่สิ่งนี้จะหยุดชะงักหากdeleteพยายามล็อคหน้าเดิมที่insertใช้งานอยู่เพราะdeleteความต้องการการล็อกหน้านั้นและinsertความต้องการการล็อคกุญแจนั้น นี้ดูเหมือนจะไม่เหมาะสมสำหรับการแทรกแม้ว่าdeleteและinsert กำลังใช้ช่วงวันที่และเวลาที่ไม่ทับซ้อนกันดังนั้นอาจมีสิ่งอื่นเกิดขึ้น

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html


4

ในกรณีที่มีคนยังดิ้นรนกับปัญหานี้:

ฉันประสบปัญหาคล้ายกันซึ่งมี 2 คำขอที่กดปุ่มเซิร์ฟเวอร์ในเวลาเดียวกัน ไม่มีสถานการณ์เหมือนด้านล่าง:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

ดังนั้นฉันจึงงงว่าทำไมการหยุดชะงักจึงเกิดขึ้น

จากนั้นฉันก็พบว่ามีความสัมพันธ์ระหว่างเด็กกับผู้ปกครองจัดส่งระหว่าง 2 ตารางเนื่องจากรหัสต่างประเทศ เมื่อฉันถูกแทรกบันทึกในตารางลูกธุรกรรมได้รับล็อคในแถวของตารางหลัก ทันทีหลังจากนั้นฉันพยายามอัปเดตแถวพาเรนต์ซึ่งเรียกใช้การยกระดับการล็อคเป็นเอกสิทธิ์ EXCLUSIVE เนื่องจากธุรกรรมที่เกิดขึ้นพร้อมกันครั้งที่ 2 ถือล็อค SHARED อยู่ทำให้เกิดการหยุดชะงัก

อ้างถึง: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


ในกรณีของฉันก็ดูเหมือนว่าปัญหาคือความสัมพันธ์ที่สำคัญต่างประเทศ Thanks1
Chris Prince

3

สำหรับโปรแกรมเมอร์ Java ที่ใช้ Spring ฉันได้หลีกเลี่ยงปัญหานี้โดยใช้มุมมอง AOP ที่ลองธุรกรรมที่เรียกใช้เป็น deadlock ชั่วคราวโดยอัตโนมัติ

ดู@RetryTransaction Javadoc สำหรับข้อมูลเพิ่มเติม


0

ฉันมีวิธีการภายในซึ่งถูกห่อใน MySqlTransaction

ปัญหาการหยุดชะงักปรากฏขึ้นสำหรับฉันเมื่อฉันวิ่งวิธีเดียวกันควบคู่ไปกับตัวเอง

ไม่มีปัญหาในการใช้งานอินสแตนซ์เดียวของวิธีการ

เมื่อฉันลบ MySqlTransaction ฉันสามารถเรียกใช้วิธีการแบบขนานกับตัวเองโดยไม่มีปัญหา

เพียงแบ่งปันประสบการณ์ของฉันฉันไม่ได้เรียกร้องอะไร


0

cronอันตราย. หากอินสแตนซ์หนึ่งของ cron ล้มเหลวก่อนที่จะครบกำหนดถัดไปพวกเขามีแนวโน้มที่จะต่อสู้ซึ่งกันและกัน

มันจะเป็นการดีกว่าถ้าคุณมีงานต่อเนื่องที่จะลบบางแถวหลับบ้างแล้วทำซ้ำ

นอกจากนี้ยังINDEX(datetime)เป็นสิ่งสำคัญมากสำหรับการหลีกเลี่ยงการหยุดชะงัก

แต่ถ้าการทดสอบวันที่และเวลามีมากกว่า 20% ของตารางDELETEจะทำการสแกนตาราง ชิ้นเล็ก ๆ ที่ถูกลบบ่อยขึ้นเป็นวิธีแก้ปัญหา

อีกสาเหตุที่ทำให้ชิ้นเล็กลงคือล็อคแถวให้น้อยลง

บรรทัดล่างสุด:

  • INDEX(datetime)
  • งานที่ทำงานอย่างต่อเนื่อง - ลบพักสักครู่ทำซ้ำ
  • เพื่อให้แน่ใจว่างานด้านบนยังไม่ตายให้มีงาน cron ที่มีวัตถุประสงค์เพื่อเริ่มต้นใหม่เมื่อล้มเหลวเท่านั้น

เทคนิคการลบอื่น ๆ : http://mysql.rjweb.org/doc.php/deletebig

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