MySQL InnoDB khóa khóa chính khi xóa ngay cả trong READ CAM KẾT


11

Lời nói đầu

Ứng dụng của chúng tôi chạy một số luồng thực thi DELETEcác truy vấn song song. Các truy vấn ảnh hưởng đến dữ liệu bị cô lập, tức là không có khả năng DELETExảy ra đồng thời trên cùng một hàng từ các luồng riêng biệt. Tuy nhiên, trên mỗi tài liệu, MySQL sử dụng khóa được gọi là khóa 'khóa tiếp theo' cho các DELETEcâu lệnh, khóa cả khóa khớp và một số khoảng cách. Điều này dẫn đến khóa chết và giải pháp duy nhất mà chúng tôi tìm thấy là sử dụng READ COMMITTEDmức cô lập.

Vấn đề

Vấn đề phát sinh khi thực hiện các DELETEcâu lệnh phức tạp với JOINs của các bảng lớn. Trong một trường hợp cụ thể, chúng tôi có một bảng với các cảnh báo chỉ có hai hàng, nhưng truy vấn cần bỏ tất cả các cảnh báo thuộc về một số thực thể cụ thể từ hai INNER JOINbảng ed riêng biệt . Truy vấn như sau:

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

Khi bảng day_poseition đủ lớn (trong trường hợp thử nghiệm của tôi có 1448 hàng) thì mọi giao dịch ngay cả với READ COMMITTEDchế độ cách ly sẽ chặn toàn bộ proc_warnings bảng.

Vấn đề luôn được sao chép trên dữ liệu mẫu này - http://yadi.sk/d/QDuwBtpW1BxB9 cả trong MySQL 5.1 (đã kiểm tra ngày 5.1.59) và MySQL 5.5 (đã kiểm tra trên MySQL 5.5.24).

EDIT: Dữ liệu mẫu được liên kết cũng chứa lược đồ và chỉ mục cho các bảng truy vấn, được sao chép ở đây để thuận tiện:

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`)
);

Truy vấn trên mỗi giao dịch như sau:

  • Giao dịch 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;
  • Giao dịch 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;

Một trong số chúng luôn thất bại với lỗi 'Khóa thời gian chờ vượt quá ...'. Các hàng information_schema.innodb_trxchứa sau:

| 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' |

Như tôi có thể thấy cả hai truy vấn đều muốn Xkhóa độc quyền trên một hàng với khóa chính = 53. Tuy nhiên, không ai trong số chúng phải xóa các hàng khỏi proc_warningsbảng. Tôi chỉ không hiểu tại sao chỉ số bị khóa. Ngoài ra, chỉ mục không bị khóa khi proc_warningsbảng trống hoặc day_positionbảng chứa số lượng hàng ít hơn (tức là một trăm hàng).

Điều tra sâu hơn là chạy EXPLAINqua SELECTtruy vấn tương tự . Nó cho thấy rằng trình tối ưu hóa truy vấn không sử dụng chỉ mục cho proc_warningsbảng truy vấn và đó là lý do duy nhất tôi có thể tưởng tượng tại sao nó chặn toàn bộ chỉ mục khóa chính.

Trường hợp đơn giản

Vấn đề cũng có thể được sao chép trong trường hợp đơn giản hơn khi chỉ có hai bảng có vài bản ghi, nhưng bảng con không có chỉ mục trên cột ref của bảng cha.

Tạo parentbảng

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

Tạo childbảng

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

Điền vào bảng

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

Thử nghiệm trong hai giao dịch song song:

  • Giao dịch 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;
  • Giao dịch 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;

Phần chung trong cả hai trường hợp là MySQL không sử dụng các chỉ mục. Tôi tin rằng đó là lý do khóa của toàn bộ bảng.

Giải pháp của chúng tôi

Giải pháp duy nhất mà chúng ta có thể thấy bây giờ là tăng thời gian chờ khóa mặc định từ 50 giây lên 500 giây để cho phép xử lý xong luồng. Sau đó giữ ngón tay chéo.

Bất kỳ trợ giúp đánh giá cao.


Tôi có một câu hỏi: Bạn đã thực hiện CAM KẾT trong bất kỳ giao dịch nào chưa?
RolandoMySQLDBA

Tất nhiên. Vấn đề là tất cả các giao dịch khác phải đợi cho đến khi một trong số chúng thực hiện thay đổi. Trường hợp thử nghiệm đơn giản không chứa câu lệnh cam kết để chỉ ra cách tái tạo vấn đề. Nếu bạn chạy cam kết hoặc khôi phục trong giao dịch không chờ đợi, nó sẽ giải phóng khóa đồng thời và giao dịch chờ hoàn thành.
Vitalidze

Khi bạn nói MySQL không sử dụng các chỉ mục trong cả hai trường hợp, có phải vì không có trong kịch bản thực không? Nếu có chỉ mục, bạn có thể cung cấp mã cho họ không? Có thể thử bất kỳ đề xuất chỉ mục được đăng dưới đây? Nếu không có chỉ mục và không thể thử thêm bất kỳ chỉ mục nào, thì MySQL không thể hạn chế tập dữ liệu được xử lý bởi mỗi luồng. Nếu đó là trường hợp thì N luồng sẽ đơn giản nhân khối lượng công việc của máy chủ với N lần và sẽ hiệu quả hơn nếu chỉ để một luồng chạy với danh sách tham số như {WHERE vd.ivehicle_id IN (2, 13) AND dp.dundred_data = 1;}.
JM Hicks

Ok, tìm thấy các chỉ mục được giấu trong tệp dữ liệu mẫu được liên kết.
JM Hicks

thêm vài câu hỏi: 1) day_positionbảng thường chứa bao nhiêu hàng , khi nào nó bắt đầu chạy chậm đến mức bạn phải giảm giới hạn thời gian chờ đến 500 giây? 2) Mất bao lâu để chạy khi bạn chỉ có dữ liệu mẫu?
JM Hicks

Câu trả lời:


3

TRẢ LỜI MỚI (SQL động kiểu MySQL): Ok, điều này đã giải quyết vấn đề theo cách mà một trong những người gửi bài khác mô tả - đảo ngược thứ tự trong đó các khóa độc quyền không tương thích lẫn nhau được thu thập sao cho dù có xảy ra bao nhiêu lần đi chăng nữa ít thời gian nhất khi kết thúc thực hiện giao dịch.

Điều này được thực hiện bằng cách tách phần đã đọc của câu lệnh thành câu lệnh chọn riêng của nó và tự động tạo ra một câu lệnh xóa sẽ bị buộc phải chạy lần cuối do thứ tự xuất hiện của câu lệnh và sẽ chỉ ảnh hưởng đến bảng Proc_warnings.

Một bản demo có sẵn tại sql fiddle:

Liên kết này hiển thị lược đồ w / dữ liệu mẫu và một truy vấn đơn giản cho các hàng khớp với ivehicle_id=2. Kết quả 2 hàng, vì không ai trong số họ đã bị xóa.

Liên kết này hiển thị cùng một lược đồ, dữ liệu mẫu, nhưng chuyển một giá trị 2 cho chương trình được lưu trữ DeleteEntries, yêu cầu SP xóa proc_warningscác mục nhập cho ivehicle_id=2. Truy vấn đơn giản cho các hàng trả về không có kết quả vì tất cả chúng đã bị xóa thành công. Bản demo liên kết chỉ hạ cấp rằng mã hoạt động như dự định xóa. Người dùng với môi trường kiểm tra thích hợp có thể nhận xét liệu điều này có giải quyết được vấn đề của luồng bị chặn hay không.

Đây là mã để thuận tiện:

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;

Đây là cú pháp để gọi chương trình từ trong một giao dịch:

CALL DeleteEntries(2);

TRẢ LỜI GỐC (vẫn nghĩ rằng nó không quá tồi tệ) Có vẻ như 2 vấn đề: 1) truy vấn chậm 2) hành vi khóa bất ngờ

Liên quan đến vấn đề # 1, các truy vấn chậm thường được giải quyết bằng hai kỹ thuật tương tự trong đơn giản hóa câu lệnh truy vấn song song và bổ sung hoặc sửa đổi hữu ích cho các chỉ mục. Bản thân bạn đã thực hiện kết nối với các chỉ mục - không có chúng, trình tối ưu hóa không thể tìm kiếm một tập hợp các hàng giới hạn để xử lý và mỗi hàng từ mỗi bảng nhân với mỗi hàng thừa sẽ quét số lượng công việc bổ sung phải được thực hiện.

ĐƯỢC SỬA ĐỔI SAU KHI XEM BÀI VIẾT VÀ CHỈ SỐ: Nhưng tôi tưởng tượng bạn sẽ nhận được lợi ích hiệu suất cao nhất cho truy vấn của mình bằng cách đảm bảo rằng bạn có cấu hình chỉ mục tốt. Để làm như vậy, bạn có thể thực hiện xóa hiệu suất tốt hơn và thậm chí có thể xóa hiệu suất tốt hơn, bằng cách đánh đổi các chỉ mục lớn hơn và có lẽ hiệu suất chèn chậm hơn đáng chú ý trên cùng các bảng có thêm cấu trúc chỉ mục được thêm vào.

SOMEWHAT TỐT HƠN:

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`)
);

ĐƯỢC SỬA ĐỔI TẠI ĐÂY

Nhưng nếu tôi có trong tay nó, tại thời điểm này, vì phải có một lượng dữ liệu tốt để làm cho nó mất nhiều thời gian, tôi sẽ chỉ đi tìm tất cả các chỉ số bao phủ để đảm bảo rằng tôi đang có được chỉ mục tốt nhất thời gian xử lý sự cố của tôi có thể mua, nếu không có gì khác để loại trừ một phần của vấn đề.

CHỈ SỐ TỐT NHẤT / BAO BÌ:

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`)
);

Có hai mục tiêu tối ưu hóa hiệu suất được tìm kiếm bởi hai đề xuất thay đổi cuối cùng:
1) Nếu các khóa tìm kiếm cho các bảng được truy cập liên tiếp không giống với các kết quả khóa được trả về cho bảng hiện đang truy cập, chúng tôi sẽ loại bỏ những gì cần phải thực hiện một tập hợp các thao tác tìm kiếm chỉ mục thứ hai trên chỉ mục được nhóm
2) Nếu không phải là trường hợp sau, thì ít nhất vẫn có khả năng trình tối ưu hóa có thể chọn thuật toán nối hiệu quả hơn vì các chỉ mục sẽ giữ nguyên các phím tham gia được yêu cầu theo thứ tự được sắp xếp.

Truy vấn của bạn có vẻ đơn giản như có thể (được sao chép ở đây trong trường hợp nó được chỉnh sửa sau):

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;

Tất nhiên trừ khi có một cái gì đó về thứ tự tham gia bằng văn bản ảnh hưởng đến cách trình tối ưu hóa truy vấn tiến hành trong trường hợp bạn có thể thử một số đề xuất viết lại mà người khác đã cung cấp, bao gồm cả gợi ý w / index này (tùy chọn):

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 = ?
    )
);

Liên quan đến # 2, hành vi khóa bất ngờ.

Như tôi có thể thấy cả hai truy vấn đều muốn khóa X độc quyền trên một hàng với khóa chính = 53. Tuy nhiên, không ai trong số chúng phải xóa các hàng khỏi bảng Proc_warnings. Tôi chỉ không hiểu tại sao chỉ số bị khóa.

Tôi đoán đó sẽ là chỉ mục bị khóa bởi vì hàng dữ liệu bị khóa nằm trong một chỉ mục được nhóm lại, tức là hàng dữ liệu duy nhất nằm trong chỉ mục.

Nó sẽ bị khóa, bởi vì:
1) theo http://dev.mysql.com/doc/refman/5.1/en/innodb-locks-set.html

... một XÓA thường thiết lập các khóa bản ghi trên mỗi bản ghi chỉ mục được quét trong quá trình xử lý câu lệnh SQL. Không có vấn đề gì nếu có các điều kiện WHERE trong câu lệnh sẽ loại trừ hàng. InnoDB không nhớ chính xác điều kiện WHERE, mà chỉ biết phạm vi chỉ mục nào được quét.

Bạn cũng đã đề cập ở trên:

... đối với tôi, tính năng chính của READ CAM KẾT là cách nó xử lý các khóa. Nó sẽ giải phóng các khóa chỉ mục của các hàng không khớp, nhưng không.

và cung cấp tài liệu tham khảo sau cho điều đó:
http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html#isolevel_read-commned

Những trạng thái giống như bạn, ngoại trừ theo cùng một tham chiếu đó, có một điều kiện mà khóa sẽ được giải phóng:

Ngoài ra, các khóa bản ghi cho các hàng không khớp được phát hành sau khi MySQL đã đánh giá điều kiện WHERE.

Điều này cũng được nhắc lại tại trang hướng dẫn này http://dev.mysql.com/doc/refman/5.1/en/innodb-record-level-locks.html

Ngoài ra còn có các hiệu ứng khác của việc sử dụng mức cô lập READ CAMITTED hoặc cho phép innodb_locks_unsafe_for_binlog: Khóa ghi cho các hàng không khớp được phát hành sau khi MySQL đã đánh giá điều kiện WHERE.

Vì vậy, chúng tôi đã nói rằng điều kiện WHERE phải được đánh giá trước khi khóa có thể được hủy bỏ. Thật không may, chúng tôi không được thông báo khi điều kiện WHERE được đánh giá và có lẽ điều gì đó có thể thay đổi từ kế hoạch này sang kế hoạch khác được tạo bởi trình tối ưu hóa. Nhưng nó cho chúng ta biết rằng việc phát hành khóa, bằng cách nào đó phụ thuộc vào hiệu suất thực hiện truy vấn, tối ưu hóa mà chúng ta thảo luận ở trên phụ thuộc vào việc viết cẩn thận câu lệnh và sử dụng chỉ mục một cách thận trọng. Nó cũng có thể được cải thiện bằng thiết kế bảng tốt hơn nhưng điều đó có lẽ sẽ tốt nhất cho một câu hỏi riêng biệt.

Ngoài ra, chỉ mục cũng không bị khóa khi bảng Proc_warnings trống

Cơ sở dữ liệu không thể khóa các bản ghi trong chỉ mục nếu không có.

Ngoài ra, chỉ mục không bị khóa khi ... bảng day_poseition chứa số lượng hàng ít hơn (tức là một trăm hàng).

Điều này có thể có nghĩa là nhiều thứ như nhưng có lẽ không giới hạn ở: một kế hoạch thực hiện khác do thay đổi số liệu thống kê, khóa quá ngắn để có thể quan sát được do thực thi nhanh hơn nhiều do tập dữ liệu nhỏ hơn nhiều tham gia hoạt động.


Điều WHEREkiện được đánh giá khi truy vấn hoàn thành. Phải không? Tôi nghĩ rằng khóa được phát hành ngay sau khi một số truy vấn đồng thời thực hiện. Đó là hành vi tự nhiên. Tuy nhiên, điều này không xảy ra. Không có truy vấn được đề xuất nào trong luồng này giúp tránh việc khóa chỉ mục cụm trong proc_warningsbảng. Tôi nghĩ rằng tôi sẽ gửi một lỗi cho MySQL. Cảm ơn bạn đã giúp đỡ.
Vitalidze

Tôi cũng không mong họ tránh được hành vi khóa. Tôi hy vọng nó sẽ bị khóa bởi vì tôi nghĩ rằng tài liệu nói rằng đó là những gì được mong đợi, cho dù đó có phải là cách chúng ta muốn nó xử lý truy vấn hay không. Tôi chỉ mong rằng việc thoát khỏi vấn đề hiệu năng sẽ giữ cho truy vấn đồng thời không bị chặn trong thời gian dài rõ ràng (hơn 500 giây).
JM Hicks

Mặc dù {WHERE} của bạn có vẻ như có thể được sử dụng trong quá trình xử lý nối để hạn chế các hàng được bao gồm trong phép tính nối, tôi không thấy mệnh đề {WHERE} của bạn có thể được đánh giá trên mỗi hàng bị khóa cho đến khi toàn bộ tập hợp được tham gia tính toán là tốt. Điều đó nói rằng, đối với phân tích của chúng tôi, tôi nghi ngờ bạn đúng rằng chúng ta nên nghi ngờ "Điều kiện WHERE được đánh giá khi hoàn thành truy vấn". Tuy nhiên, điều đó dẫn tôi đến cùng một kết luận chung, rằng hiệu suất cần phải được giải quyết, và sau đó mức độ đồng thời rõ ràng sẽ tăng tỷ lệ thuận.
JM Hicks

Hãy nhớ rằng các chỉ mục thích hợp có khả năng loại bỏ bất kỳ quét toàn bộ bảng nào xảy ra trên bảng Proc_warnings. Để điều đó xảy ra, chúng tôi cần trình tối ưu hóa truy vấn để hoạt động tốt cho chúng tôi và chúng tôi cần các chỉ mục, truy vấn và dữ liệu của chúng tôi để đồng nghiệp độc đáo với nó. Các giá trị tham số phải đánh giá ở cuối các hàng trong bảng đích không trùng nhau giữa hai truy vấn. Các chỉ mục cần cung cấp cho trình tối ưu hóa truy vấn một phương tiện để tìm kiếm hiệu quả các hàng đó. Chúng tôi cần trình tối ưu hóa để nhận ra rằng hiệu quả tìm kiếm tiềm năng và chọn một kế hoạch như vậy.
JM Hicks

Nếu tất cả đều ổn giữa các giá trị tham số, chỉ mục, kết quả không chồng lấp trong bảng Proc_warnings và lựa chọn gói tối ưu hóa, ngay cả khi các khóa có thể được tạo trong khoảng thời gian cần thiết để thực hiện truy vấn cho từng luồng, các khóa đó, nếu không chồng chéo, sẽ không xung đột với các yêu cầu khóa của các luồng khác.
JM Hicks

3

Tôi có thể thấy READ_COMMITTED có thể gây ra tình huống này như thế nào .

READ_COMMITTED cho phép ba điều:

  • Khả năng hiển thị các thay đổi đã cam kết của các giao dịch khác bằng cách sử dụng mức cô lập READ_COMMITTED .
  • Đọc không lặp lại: Giao dịch thực hiện cùng một lần truy xuất với khả năng nhận được kết quả khác nhau mỗi lần.
  • Phantoms: Giao dịch có thể có hàng xuất hiện ở nơi không thể nhìn thấy trước.

Điều này tạo ra một mô hình nội bộ cho chính giao dịch vì giao dịch phải duy trì liên lạc với:

  • Nhóm đệm InnoDB (trong khi cam kết vẫn chưa được xử lý)
  • Khóa chính của bảng
  • Có khả năng
    • bộ đệm ghi đôi
    • Hoàn tác không gian bảng
  • Ảnh đại diện

Nếu hai giao dịch READ_COMMITTED riêng biệt đang truy cập vào cùng một bảng / hàng đang được cập nhật theo cùng một cách, hãy sẵn sàng để mong đợi không phải là khóa bảng, mà là một khóa độc quyền trong gen_clust_index (còn gọi là Chỉ mục cụm) . Đưa ra các truy vấn từ trường hợp đơn giản của bạn:

  • Giao dịch 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;
  • Giao dịch 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;

Bạn đang khóa cùng một vị trí trong gen_clust_index. Người ta có thể nói, "nhưng mỗi giao dịch có một khóa chính khác nhau." Thật không may, đây không phải là trường hợp trong mắt của InnoDB. Nó chỉ xảy ra khi id 1 và id 2 nằm trên cùng một trang.

Nhìn lại information_schema.innodb_locksbạn được cung cấp trong Câu hỏi

| 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' |

Ngoại trừ lock_id, lock_trx_idphần còn lại của mô tả khóa là giống hệt nhau. Vì các giao dịch nằm trên cùng một sân chơi (cách ly giao dịch giống nhau), điều này thực sự sẽ xảy ra .

Hãy tin tôi, tôi đã giải quyết loại tình huống này trước đây. Đây là bài viết trước đây của tôi về điều này:


Tôi đã đọc về những điều bạn mô tả trong tài liệu MySQL. Nhưng đối với tôi, tính năng chính của READ CAM KẾT là cách nó xử lý các khóa . Nó sẽ giải phóng các khóa chỉ mục của các hàng không khớp, nhưng không.
Vitalidze

Nếu chỉ một câu lệnh SQL được khôi phục do lỗi, một số khóa được đặt bởi câu lệnh có thể được giữ nguyên. Điều này xảy ra do InnoDB lưu trữ khóa hàng theo định dạng mà sau đó không thể biết khóa nào được đặt bởi câu lệnh nào: dev.mysql.com/doc/refman/5.5/en/innodb-deadlock-detection.html
RolandoMyQueryDBA

Xin lưu ý rằng tôi đã đề cập đến khả năng hai hàng tồn tại trong cùng một trang để khóa (Xem Look back at information_schema.innodb_locks you supplied in the Question)
RolandoMyQueryDBA

Về việc khôi phục lại một tuyên bố - Tôi hiểu điều này như thể một tuyên bố thất bại trong một giao dịch duy nhất, nó vẫn có thể giữ các khóa. Vậy là được rồi. Câu hỏi lớn của tôi là tại sao nó không phát hành khóa hàng không khớp sau khi xử lý thành công DELETEcâu lệnh.
Vitalidze

Với hai khóa hoàn thành, một phải được cuộn lại. Có thể là ổ khóa có thể kéo dài. LÝ THUYẾT LÀM VIỆC: giao dịch được khôi phục có thể thử lại và có thể gặp phải một khóa cũ từ giao dịch trước đó đã giữ nó.
RolandoMySQLDBA

2

Tôi nhìn vào truy vấn và giải thích. Tôi không chắc chắn, nhưng có một cảm giác ruột, rằng vấn đề là sau đây. Hãy xem truy vấn:

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;

CHỌN tương đương là:

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;

Nếu bạn nhìn vào giải thích của nó, bạn sẽ thấy rằng kế hoạch thực hiện bắt đầu với proc_warningsbảng. Điều đó có nghĩa là MySQL quét khóa chính trong bảng và cho mỗi hàng kiểm tra xem điều kiện có đúng không và nếu đúng - hàng bị xóa. Đó là MySQL phải khóa toàn bộ khóa chính.

Những gì bạn cần là đảo ngược thứ tự THAM GIA, đó là tìm tất cả các id giao dịch vd.ivehicle_id=16 AND dp.dirty_data=1và tham gia chúng trên proc_warningsbàn.

Đó là bạn sẽ cần phải vá một trong các chỉ số:

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

và viết lại truy vấn xóa:

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;

Thật không may, điều này không có ích, tức là các hàng trong proc_warningsvẫn bị khóa. Dẫu sao cũng xin cảm ơn.
Vitalidze

2

Khi bạn đặt mức giao dịch mà không theo cách bạn thực hiện, chỉ áp dụng Đọc cam kết cho giao dịch tiếp theo, do đó (đặt cam kết tự động). Điều này có nghĩa là sau autocommit = 0, bạn không còn trong Read Commised nữa. Tôi sẽ viết nó theo cách này:

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;

Bạn có thể kiểm tra mức độ cô lập mà bạn tham gia bằng cách truy vấn

SELECT @@tx_isolation;

Đo không phải sự thật. Tại sao SET AUTOCOMMIT=0nên thiết lập lại mức cô lập cho giao dịch tiếp theo? Tôi tin rằng nó bắt đầu một giao dịch mới nếu không có giao dịch nào được bắt đầu trước đó (đó là trường hợp của tôi). Vì vậy, để chính xác hơn thì tiếp theo START TRANSACTIONhoặc BEGINtuyên bố là không cần thiết. Mục đích của tôi để vô hiệu hóa autocommit là để giao dịch được mở sau khi DELETEthực hiện câu lệnh.
Vitalidze

1
@SqlKiwi đây là cách để chỉnh sửa bài đăng này và đây là cách để bình luận về ;-)
jcolebrand
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.