MySQL InnoDB ล็อคกุญแจหลักในการลบแม้ใน READ COMMITTED


11

คำนำ

แอปพลิเคชันของเราทำงานหลายเธรดที่ดำเนินการDELETEค้นหาแบบขนาน แบบสอบถามมีผลต่อข้อมูลที่แยกได้เช่นไม่ควรมีความเป็นไปได้ที่DELETEเกิดขึ้นพร้อมกันในแถวเดียวกันจากกระทู้แยก อย่างไรก็ตามตามเอกสาร MySQL ใช้ล็อคที่เรียกว่า 'คีย์ต่อไป' สำหรับDELETEงบซึ่งล็อคทั้งคีย์การจับคู่และช่องว่างบางส่วน สิ่งนี้นำไปสู่การล็อกที่ตายแล้วและทางออกเดียวที่เราพบคือใช้READ COMMITTEDระดับการแยก

ปัญหา

ปัญหาเกิดขึ้นเมื่อรันDELETEคำสั่งที่ซับซ้อนด้วยJOINs ของตารางขนาดใหญ่ ในบางกรณีเรามีตารางที่มีคำเตือนที่มีเพียงสองแถว แต่แบบสอบถามจำเป็นต้องวางคำเตือนทั้งหมดที่อยู่ในเอนทิตี้บางแห่งจากINNER JOINตาราง ed แยกสองตาราง แบบสอบถามมีดังนี้:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1

เมื่อตาราง day_position มีขนาดใหญ่พอ (ในกรณีทดสอบของฉันมีแถว 1448) ธุรกรรมใด ๆ แม้ด้วยREAD COMMITTEDโหมดแยกบล็อกตารางทั้งหมด proc_warnings

ปัญหานี้จะมีการทำซ้ำในข้อมูลตัวอย่างนี้ - http://yadi.sk/d/QDuwBtpW1BxB9ทั้งใน MySQL 5.1 (ตรวจสอบใน 5.1.59) และ MySQL 5.5 (ตรวจสอบใน MySQL 5.5.24)

แก้ไข: ข้อมูลตัวอย่างที่เชื่อมโยงยังมีสคีมาและดัชนีสำหรับตารางคิวรีทำซ้ำที่นี่เพื่อความสะดวก:

CREATE TABLE  `proc_warnings` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned NOT NULL,
    `warning` varchar(2048) NOT NULL,
    PRIMARY KEY (`id`),
    KEY `proc_warnings__transaction` (`transaction_id`)
);

CREATE TABLE  `day_position` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `transaction_id` int(10) unsigned DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_day_id` int(10) unsigned DEFAULT NULL,
    `dirty_data` tinyint(4) DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `day_position__trans` (`transaction_id`),
    KEY `day_position__is` (`ivehicle_day_id`,`sort_index`),
    KEY `day_position__id` (`ivehicle_day_id`,`dirty_data`)
) ;

CREATE TABLE  `ivehicle_days` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `d` date DEFAULT NULL,
    `sort_index` int(11) DEFAULT NULL,
    `ivehicle_id` int(10) unsigned DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `ivehicle_days__is` (`ivehicle_id`,`sort_index`),
    KEY `ivehicle_days__d` (`d`)
);

แบบสอบถามต่อการทำธุรกรรมมีดังนี้:

  • ธุรกรรม 1

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;
  • ธุรกรรม 2

    set transaction isolation level read committed;
    set autocommit=0;
    begin;
    DELETE pw 
    FROM proc_warnings pw 
    INNER JOIN day_position dp 
        ON dp.transaction_id = pw.transaction_id 
    INNER JOIN ivehicle_days vd 
        ON vd.id = dp.ivehicle_day_id 
    WHERE vd.ivehicle_id=13 AND dp.dirty_data=1;

หนึ่งในนั้นล้มเหลวเสมอกับข้อผิดพลาด 'เกินเวลารอการล็อคเกิน ... ' information_schema.innodb_trxมีจำนวนแถวต่อไปนี้:

| trx_id     | trx_state   | trx_started           | trx_requested_lock_id  | trx_wait_started      | trx_wait | trx_mysql_thread_id | trx_query |
| '1A2973A4' | 'LOCK WAIT' | '2012-12-12 20:03:25' | '1A2973A4:0:3172298:2' | '2012-12-12 20:03:25' | '2'      | '3089'              | 'DELETE pw FROM proc_warnings pw INNER JOIN day_position dp ON dp.transaction_id = pw.transaction_id INNER JOIN ivehicle_days vd ON vd.id = dp.ivehicle_day_id WHERE vd.ivehicle_id=13 AND dp.dirty_data=1' |
| '1A296F67' | 'RUNNING'   | '2012-12-12 19:58:02' | NULL                   | NULL | '7' | '3087' | NULL |

information_schema.innodb_locks

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

ดังที่ฉันเห็นทั้งสองข้อความค้นหาต้องการการXล็อกแบบเอกสิทธิ์เฉพาะบุคคลในแถวที่มีคีย์หลัก = 53 อย่างไรก็ตามทั้งสองแบบสอบถามไม่ต้องลบแถวออกจากproc_warningsตาราง ฉันไม่เข้าใจว่าทำไมดัชนีจึงถูกล็อค ยิ่งไปกว่านั้นดัชนีจะไม่ถูกล็อกเมื่อproc_warningsตารางว่างเปล่าหรือday_positionตารางมีจำนวนแถวน้อยลง (เช่นหนึ่งร้อยแถว)

การตรวจสอบเพิ่มเติมคือการเรียกใช้แบบสอบถามที่EXPLAINคล้ายกัน SELECTมันแสดงให้เห็นว่าเครื่องมือเพิ่มประสิทธิภาพการสืบค้นไม่ได้ใช้ดัชนีเพื่อสืบค้นproc_warningsตารางและนั่นเป็นเหตุผลเดียวที่ฉันสามารถจินตนาการได้ว่าเหตุใดจึงปิดกั้นดัชนีคีย์หลักทั้งหมด

กรณีที่ง่าย

ปัญหายังสามารถทำซ้ำได้ในกรณีที่ง่ายกว่าเมื่อมีเพียงสองตารางที่มีระเบียนไม่กี่ตาราง แต่ตารางลูกไม่ได้มีดัชนีในคอลัมน์อ้างอิงตารางหลัก

สร้างparentตาราง

CREATE TABLE `parent` (
  `id` int(10) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

สร้างchildตาราง

CREATE TABLE `child` (
  `id` int(10) unsigned NOT NULL,
  `parent_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB

เติมตาราง

INSERT INTO `parent` (id) VALUES (1), (2);
INSERT INTO `child` (id, parent_id) VALUES (1, NULL), (2, NULL);

ทดสอบในการทำธุรกรรมสองขนาน:

  • ธุรกรรม 1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • ธุรกรรม 2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

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

ทางออกของเรา

ทางออกเดียวที่เราเห็นได้ในตอนนี้คือเพิ่มการหมดเวลารอการล็อกเริ่มต้นจาก 50 วินาทีเป็น 500 วินาทีเพื่อให้เธรดหมดสิ้น จากนั้นให้นิ้วไขว้กัน

ความช่วยเหลือใด ๆ ที่ชื่นชม


ฉันมีคำถาม: คุณเรียกใช้งาน COMMIT ในธุรกรรมใด ๆ หรือไม่?
RolandoMySQLDBA

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

เมื่อคุณบอกว่า MySQL ไม่ใช้ดัชนีไม่ว่าในกรณีใดเป็นเพราะในสถานการณ์จริงไม่มีหรือไม่? หากมีดัชนีคุณสามารถให้รหัสสำหรับพวกเขาได้หรือไม่? เป็นไปได้หรือไม่ที่จะลองใช้คำแนะนำเกี่ยวกับดัชนีที่โพสต์ด้านล่าง หากไม่มีดัชนีและไม่สามารถลองเพิ่มได้ MySQL จะไม่สามารถ จำกัด ชุดข้อมูลที่ประมวลผลโดยแต่ละเธรด หากเป็นกรณีนี้แล้วเธรด N จะเพิ่มจำนวนเวิร์กโหลดของเซิร์ฟเวอร์เพียง N ครั้งเท่านั้นและจะมีประสิทธิภาพมากกว่าที่จะปล่อยให้หนึ่งเธรดรันด้วยรายการพารามิเตอร์เช่น {WHERE vd.ivehicle_id IN (2, 13) และ dp.dirty_data = 1;}
JM Hicks

ตกลงพบดัชนีซ่อนตัวอยู่ในไฟล์ข้อมูลตัวอย่างที่เชื่อมโยง
JM Hicks

คำถามเพิ่มเติมสองสามข้อ: 1) day_positionตารางมีจำนวนแถวเท่าไหร่เมื่อเริ่มทำงานช้าจนคุณต้องชน จำกัด การหมดเวลา 500 วินาที 2) ใช้เวลานานแค่ไหนเมื่อคุณมีเพียงข้อมูลตัวอย่าง?
JM Hicks

คำตอบ:


3

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

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

ตัวอย่างสามารถดูได้ที่ซอ fl:

นี้การเชื่อมโยงการแสดงคี w / ivehicle_id=2ข้อมูลตัวอย่างและแบบสอบถามง่ายๆสำหรับแถวที่ตรงกับ ผลลัพธ์ 2 แถวเนื่องจากไม่มีการลบเลย

นี้การเชื่อมโยงการแสดงเค้าร่างเดียวกันข้อมูลตัวอย่าง แต่ส่งค่า 2 DeleteEntries โปรแกรมเก็บไว้บอก SP เพื่อลบรายการสำหรับproc_warnings ivehicle_id=2แบบสอบถามแบบง่ายสำหรับแถวจะไม่ส่งคืนผลลัพธ์เนื่องจากลบทั้งหมดเรียบร้อยแล้ว ลิงก์สาธิตจะสาธิตเฉพาะว่าโค้ดทำงานตามที่ตั้งใจจะลบเท่านั้น ผู้ใช้ที่มีสภาพแวดล้อมการทดสอบที่เหมาะสมสามารถแสดงความคิดเห็นว่าสิ่งนี้แก้ปัญหาของเธรดที่ถูกบล็อก

นี่คือรหัสเช่นกันเพื่อความสะดวก:

CREATE PROCEDURE DeleteEntries (input_vid INT)
BEGIN

    SELECT @idstring:= '';
    SELECT @idnum:= 0;
    SELECT @del_stmt:= '';

    SELECT @idnum:= @idnum+1 idnum_col, @idstring:= CONCAT(@idstring, CASE WHEN CHARACTER_LENGTH(@idstring) > 0 THEN ',' ELSE '' END, CAST(id AS CHAR(10))) idstring_col
    FROM proc_warnings
    WHERE EXISTS (
        SELECT 0
        FROM day_position
        WHERE day_position.transaction_id = proc_warnings.transaction_id
        AND day_position.dirty_data = 1
        AND EXISTS (
            SELECT 0
            FROM ivehicle_days
            WHERE ivehicle_days.id = day_position.ivehicle_day_id
            AND ivehicle_days.ivehicle_id = input_vid
        )
    )
    ORDER BY idnum_col DESC
    LIMIT 1;

    IF (@idnum > 0) THEN
        SELECT @del_stmt:= CONCAT('DELETE FROM proc_warnings WHERE id IN (', @idstring, ');');

        PREPARE del_stmt_hndl FROM @del_stmt;
        EXECUTE del_stmt_hndl;
        DEALLOCATE PREPARE del_stmt_hndl;
    END IF;
END;

นี่เป็นรูปแบบของการเรียกโปรแกรมจากภายในธุรกรรม:

CALL DeleteEntries(2);

คำตอบเดิม (ยังคงคิดว่ามันไม่โทรมเกินไป) ดูเหมือนว่า 2 ประเด็น: 1) แบบสอบถามช้า 2) พฤติกรรมการล็อคที่ไม่คาดคิด

ในส่วนที่เกี่ยวกับปัญหาที่ 1 การสืบค้นที่ช้านั้นมักจะได้รับการแก้ไขโดยเทคนิคสองอย่างเดียวกันในการทำให้คำสั่งการสืบค้นแบบง่าย ๆ และการเพิ่มหรือการแก้ไขดัชนีที่มีประโยชน์ คุณเองได้ทำการเชื่อมต่อกับดัชนีแล้ว - หากไม่มีเครื่องมือเพิ่มประสิทธิภาพเหล่านี้จะไม่สามารถค้นหาชุดของแถวที่ จำกัด เพื่อดำเนินการและแต่ละแถวจากตารางแต่ละตัวคูณต่อแถวเพิ่มเติมสแกนปริมาณงานพิเศษที่ต้องทำ

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

ค่อนข้างดีกว่า:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd` (`dirty_data`, `ivehicle_day_id`)

) ;


CREATE TABLE  `ivehicle_days` (
    ...,
    KEY `ivehicle_days__vid_no_sort_index` (`ivehicle_id`)
);

กลับมาที่นี่ด้วย: เนื่องจากต้องใช้เวลานานเท่าที่จะทำงานได้ฉันจะปล่อยให้สกปรก_dataในดัชนีและฉันเข้าใจผิดด้วยเช่นกันเมื่อฉันวางไว้หลังจาก ivehicle_day_id ตามลำดับดัชนี - ควรเป็นอันดับแรก

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

ดัชนีความครอบคลุมที่ดีที่สุด:

CREATE TABLE  `day_position` (
    ...,
    KEY `day_position__id_rvrsd_trnsid_cvrng` (`dirty_data`, `ivehicle_day_id`, `transaction_id`)
) ;

CREATE TABLE  `ivehicle_days` (
    ...,
    UNIQUE KEY `ivehicle_days__vid_id_cvrng` (ivehicle_id, id)
);

CREATE TABLE  `proc_warnings` (

    .., /*rename primary key*/
    CONSTRAINT pk_proc_warnings PRIMARY KEY (id),
    UNIQUE KEY `proc_warnings__transaction_id_id_cvrng` (`transaction_id`, `id`)
);

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

ข้อความค้นหาของคุณดูเหมือนง่ายขึ้นเท่าที่จะทำได้ (คัดลอกมาที่นี่ในกรณีที่มีการแก้ไขในภายหลัง):

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
    ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
    ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=2 AND dp.dirty_data=1;

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

DELETE FROM proc_warnings
FORCE INDEX (`proc_warnings__transaction_id_id_cvrng`, `pk_proc_warnings`)
WHERE EXISTS (
    SELECT 0
    FROM day_position
    FORCE INDEX (`day_position__id_rvrsd_trnsid_cvrng`)  
    WHERE day_position.transaction_id = proc_warnings.transaction_id
    AND day_position.dirty_data = 1
    AND EXISTS (
        SELECT 0
        FROM ivehicle_days
        FORCE INDEX (`ivehicle_days__vid_id_cvrng`)  
        WHERE ivehicle_days.id = day_position.ivehicle_day_id
        AND ivehicle_days.ivehicle_id = ?
    )
);

ตามที่ # 2 พฤติกรรมการล็อคที่ไม่คาดคิด

ตามที่ฉันเห็นทั้งสองแบบสอบถามต้องการล็อก X แบบเอกสิทธิ์เฉพาะบุคคลในแถวที่มีคีย์หลัก = 53 อย่างไรก็ตามทั้งสองแบบสอบถามไม่ต้องลบแถวออกจากตาราง proc_warnings ฉันไม่เข้าใจว่าทำไมดัชนีจึงถูกล็อค

ฉันเดาว่ามันจะเป็นดัชนีที่ถูกล็อกเพราะแถวของข้อมูลที่จะถูกล็อคนั้นอยู่ในดัชนีแบบกลุ่มนั่นคือแถวข้อมูลแถวเดียวนั้นอยู่ในดัชนี

มันจะถูกล็อคเพราะ:
1) ตามhttp://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.html

... โดยทั่วไป DELETE จะตั้งค่าการล็อกเรคคอร์ดในทุก ๆ ดัชนีเรคคอร์ดที่ถูกสแกนในการประมวลผลคำสั่ง SQL มันไม่สำคัญว่าจะมีเงื่อนไขใดในคำสั่งที่จะแยกแถว InnoDB ไม่จำเงื่อนไข WHERE ที่แน่นอน แต่รู้เพียงว่าช่วงดัชนีใดถูกสแกน

คุณยังกล่าวถึงข้างต้น:

... สำหรับฉันคุณสมบัติหลักของ READ COMMITTED คือวิธีจัดการกับล็อค ควรปล่อยการล็อกดัชนีของแถวที่ไม่ตรงกัน แต่ไม่ควรทำ

และให้การอ้างอิงต่อไปนี้:
http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-committed

ซึ่งระบุเช่นเดียวกับคุณยกเว้นว่าอ้างอิงตามเงื่อนไขเดียวกันมีเงื่อนไขว่าจะปลดล็อค:

นอกจากนี้การล็อกเรคคอร์ดสำหรับแถวที่ไม่เข้าคู่จะถูกปล่อยออกมาหลังจากที่ MySQL ประเมินสภาพ WHERE

ซึ่งมีการกล่าวซ้ำเช่นกันในหน้าคู่มือนี้http://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html

นอกจากนี้ยังมีเอฟเฟกต์อื่น ๆ ของการใช้ระดับการแยก READ COMMITTED หรือการเปิดใช้งาน innodb_locks_unsafe_for_binlog: การล็อกเรคคอร์ดสำหรับแถวที่ไม่เข้าคู่จะถูกปล่อยออกมาหลังจากที่ MySQL ประเมินสภาพ WHERE

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

นอกจากนี้ดัชนีจะไม่ถูกล็อคอย่างใดอย่างหนึ่งเมื่อตาราง proc_warnings ว่างเปล่า

ฐานข้อมูลไม่สามารถล็อคบันทึกภายในดัชนีถ้าไม่มี

นอกจากนี้ดัชนีจะไม่ถูกล็อคเมื่อ ... ตาราง day_position มีจำนวนแถวน้อยลง (เช่นหนึ่งร้อยแถว)

สิ่งนี้อาจหมายถึงหลายสิ่งหลายอย่างเช่น แต่อาจไม่ จำกัด เพียง: แผนการดำเนินการที่แตกต่างกันเนื่องจากการเปลี่ยนแปลงของสถิติ, การล็อกที่สั้นเกินไปเกินกว่าจะสังเกตได้เนื่องจากการดำเนินการที่เร็วกว่ามากเนื่องจากชุดข้อมูลขนาดเล็กมาก / เข้าร่วมดำเนินการ


WHEREสภาพถูกประเมินเมื่อเสร็จสมบูรณ์แบบสอบถาม ไม่ใช่เหรอ ฉันคิดว่าล็อคจะถูกปล่อยทันทีหลังจากมีการค้นหาพร้อมกันบางคำสั่ง นั่นเป็นพฤติกรรมตามธรรมชาติ อย่างไรก็ตามสิ่งนี้ไม่ได้เกิดขึ้น แบบสอบถามที่แนะนำในเธรดนี้ไม่ช่วยในการหลีกเลี่ยงการล็อกดัชนีคลัสเตอร์ในproc_warningsตาราง ฉันคิดว่าฉันจะส่งข้อผิดพลาดไปยัง MySQL ขอบคุณสำหรับความช่วยเหลือของคุณ.
vitalidze

ฉันไม่คาดหวังให้พวกเขาหลีกเลี่ยงพฤติกรรมการล็อคเช่นกัน ฉันคาดหวังให้ล็อคเพราะฉันคิดว่าเอกสารบอกว่าเป็นสิ่งที่คาดหวังหรือไม่นั้นเป็นวิธีที่เราต้องการให้ประมวลผลแบบสอบถาม ฉันแค่คาดหวังว่าการกำจัดปัญหาด้านประสิทธิภาพจะทำให้คิวรีแบบพร้อมกันไม่สามารถบล็อกได้สำหรับการใช้เวลานานกว่า 500+ วินาที
JM Hicks

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

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

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

3

ฉันเห็นว่าREAD_COMMITTEDสามารถทำให้เกิดสถานการณ์นี้ได้อย่างไร

READ_COMMITTEDอนุญาตสามสิ่ง:

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

สิ่งนี้สร้างกระบวนทัศน์ภายในสำหรับธุรกรรมเองเนื่องจากธุรกรรมต้องรักษาการติดต่อกับ:

  • InnoDB Buffer Pool (ในขณะที่คอมมิชยังคงไม่ได้ฟลัช)
  • คีย์หลักของตาราง
  • อาจ
    • บัฟเฟอร์การเขียนสองครั้ง
    • เลิกทำ Tablespace
  • การเป็นตัวแทนภาพ

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

  • ธุรกรรม 1

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 1;
  • ธุรกรรม 2

    SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
    SET AUTOCOMMIT=0;
    BEGIN;
    DELETE c FROM child c 
      INNER JOIN parent p ON p.id = c.parent_id 
    WHERE p.id = 2;

คุณกำลังล็อคตำแหน่งเดียวกันใน gen_clust_index หนึ่งอาจพูดว่า "แต่แต่ละธุรกรรมมีคีย์หลักที่แตกต่างกัน" น่าเสียดายที่นี่ไม่ใช่คดีในสายตาของ InnoDB มันเกิดขึ้นเพียงว่า id 1 และ id 2 อยู่ในหน้าเดียวกัน

มองย้อนกลับไปที่information_schema.innodb_locksคุณให้มาในคำถาม

| lock_id                | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
| '1A2973A4:0:3172298:2' | '1A2973A4'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |
| '1A296F67:0:3172298:2' | '1A296F67'  | 'X'       | 'RECORD'  | '`deadlock_test`.`proc_warnings`' | '`PRIMARY`' | '0' | '3172298' | '2' | '53' |

ด้วยข้อยกเว้นของlock_id, lock_trx_idรายละเอียดการล็อคที่เหลือก็เหมือนกัน ตั้งแต่การทำธุรกรรมที่อยู่บนสนามระดับเดียวกันเล่น (แยกรายการเดียวกัน) นี้แน่นอนควรจะเกิดขึ้น

เชื่อฉันฉันได้กล่าวถึงสถานการณ์แบบนี้มาก่อน นี่คือโพสต์ที่ผ่านมาของฉันในนี้:


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

หากมีการย้อนกลับคำสั่ง SQL เพียงคำเดียวเนื่องจากข้อผิดพลาดการล็อกบางคำสั่งอาจถูกรักษาไว้ สิ่งนี้เกิดขึ้นเนื่องจาก InnoDB เก็บการล็อกแถวในรูปแบบดังกล่าวซึ่งไม่สามารถทราบได้ว่ามีการตั้งค่าการล็อกใดหลังจากนั้น: dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html
RolandoMySQLDBA

โปรดทราบว่าฉันพูดถึงความเป็นไปได้ของสองแถวที่มีอยู่ในหน้าเดียวกันสำหรับการล็อค (ดูLook back at information_schema.innodb_locks you supplied in the Question)
RolandoMySQLDBA

เกี่ยวกับการย้อนกลับคำสั่งเดียว - ฉันเข้าใจสิ่งนี้ราวกับว่าคำสั่งเดียวล้มเหลวในการทำรายการเดียวอาจยังคงล็อคอยู่ ไม่เป็นไร. คำถามใหญ่ของฉันคือทำไมมันไม่ปล่อยการล็อกแถวที่ไม่ตรงกันหลังจากประมวลผลDELETEคำสั่งเรียบร้อยแล้ว
vitalidze

ด้วยการล็อกสองอันหนึ่งอันจะต้องย้อนกลับ มีความเป็นไปได้ที่ล็อคอาจอิทธิพล ทฤษฎีการทำงาน: ธุรกรรมที่ย้อนกลับอาจลองอีกครั้งและอาจพบว่ามีการล็อกเก่าจากธุรกรรมก่อนหน้านี้ที่ถือไว้
RolandoMySQLDBA

2

ฉันดูคำถามและอธิบาย ฉันไม่แน่ใจ แต่มีความรู้สึกว่ามีปัญหาดังต่อไปนี้ ลองดูที่แบบสอบถาม:

DELETE pw 
FROM proc_warnings pw 
INNER JOIN day_position dp 
   ON dp.transaction_id = pw.transaction_id 
INNER JOIN ivehicle_days vd 
   ON vd.id = dp.ivehicle_day_id 
WHERE vd.ivehicle_id=? AND dp.dirty_data=1;

SELECT ที่เทียบเท่าคือ:

SELECT pw.id
FROM proc_warnings pw
INNER JOIN day_position dp
   ON dp.transaction_id = pw.transaction_id
INNER JOIN ivehicle_days vd
   ON vd.id = dp.ivehicle_day_id
WHERE vd.ivehicle_id=16 AND dp.dirty_data=1;

หากคุณดูคำอธิบายคุณจะเห็นว่าแผนการดำเนินการเริ่มต้นด้วยproc_warningsตาราง นั่นหมายความว่า MySQL สแกนคีย์หลักในตารางและสำหรับแต่ละแถวตรวจสอบว่าเงื่อนไขเป็นจริงหรือไม่และถ้าเป็น - แถวนั้นจะถูกลบ นั่นคือ MySQL จะต้องล็อคกุญแจหลักทั้งหมด

สิ่งที่คุณต้องการคือการกลับคำสั่งเข้าร่วมนั่นคือค้นหารหัสธุรกรรมทั้งหมดด้วยvd.ivehicle_id=16 AND dp.dirty_data=1และเข้าร่วมในproc_warningsตาราง

นั่นคือคุณจะต้องแก้ไขหนึ่งในดัชนี:

ALTER TABLE `day_position`
 DROP INDEX `day_position__id`,
 ADD INDEX `day_position__id`
   USING BTREE (`ivehicle_day_id`, `dirty_data`, `transaction_id`);

และเขียนแบบสอบถามลบอีกครั้ง:

DELETE pw
FROM (
  SELECT DISTINCT dp.transaction_id
  FROM ivehicle_days vd
  JOIN day_position dp ON dp.ivehicle_day_id = vd.id
  WHERE vd.ivehicle_id=? AND dp.dirty_data=1
) as tr_id
JOIN proc_warnings pw ON pw.transaction_id = tr_id.transaction_id;

น่าเสียดายที่นี่ไม่ได้ช่วยนั่นคือแถวproc_warningsถูกล็อคอยู่ ขอบคุณอยู่ดี
vitalidze

2

เมื่อคุณตั้งค่าระดับการทำธุรกรรมโดยไม่ใช้วิธีที่คุณใช้นั้นจะอ่านการกระทำที่มุ่งมั่นกับการทำธุรกรรมต่อไปเท่านั้นดังนั้น (ตั้งกระทำอัตโนมัติ) ค่าเฉลี่ยนี้หลังจาก autocommit = 0 คุณไม่ได้อยู่ใน Read Committed อีกต่อไป ฉันจะเขียนแบบนี้:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
DELETE c FROM child c
INNER JOIN parent p ON
    p.id = c.parent_id
WHERE p.id = 1;

คุณสามารถตรวจสอบระดับการแยกที่คุณอยู่โดยการสอบถาม

SELECT @@tx_isolation;

ที่ไม่เป็นความจริง. เหตุใดจึงSET AUTOCOMMIT=0ควรรีเซ็ตระดับการแยกสำหรับธุรกรรมถัดไป ฉันเชื่อว่ามันเริ่มทรานแซกชันใหม่ถ้าไม่มีใครเริ่มก่อน (ซึ่งเป็นกรณีของฉัน) ดังนั้นเพื่อให้แม่นยำยิ่งขึ้นคำสั่งต่อไปSTART TRANSACTIONหรือBEGINไม่จำเป็นต้องมี จุดประสงค์ของฉันในการปิดการใช้งาน autocommit ก็คือการปล่อยให้ธุรกรรมเปิดหลังจากการDELETEดำเนินการคำสั่ง
vitalidze

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