Đâ là một câu hỏi tuyệt vời. InnoDB là một công cụ khóa mức hàng, nhưng nó phải đặt các khóa bổ sung để đảm bảo an toàn với nhật ký nhị phân (được sử dụng để sao chép; khôi phục tại thời điểm). Để bắt đầu giải thích nó, hãy xem xét ví dụ (ngây thơ) sau:
session1> START TRANSACTION;
session1> DELETE FROM users WHERE is_deleted = 1; # 1 row matches (user_id 10), deleted.
session2> START TRANSACTION;
session2> UPDATE users SET is_deleted = 1 WHERE user_id = 5; # 1 row matches.
session2> COMMIT;
session1> COMMIT;
Bởi vì các câu lệnh chỉ được ghi vào nhật ký nhị phân sau khi được cam kết, trên phiên nô lệ số 2 sẽ được áp dụng trước tiên và sẽ tạo ra một kết quả khác, dẫn đến hỏng dữ liệu .
Vì vậy, những gì InnoDB làm, là thiết lập các khóa bổ sung. Nếu is_deleted
được lập chỉ mục, sau đó trước khi session1 cam kết, không ai khác sẽ có thể sửa đổi hoặc chèn vào phạm vi trong tổng số các bản ghi trong đó is_deleted=1
. Nếu không có chỉ mục nào trên is_deleted
, thì InnoDB cần khóa mọi hàng trong toàn bộ bảng để đảm bảo việc phát lại theo đúng thứ tự. Bạn có thể coi điều này là khóa khoảng cách , đó là khái niệm khác với khóa trực tiếp cấp hàng .
Trong trường hợp của bạn với ORDER BY position ASC
đó , InnoDB cần đảm bảo rằng không có hàng mới nào có thể được sửa đổi giữa giá trị khóa thấp nhất và giá trị thấp nhất có thể "đặc biệt". Nếu bạn đã làm điều gì đó như ORDER BY position DESC
.. tốt, sau đó không ai có thể chèn vào phạm vi này.
Vì vậy, đây là giải pháp:
-
Ghi nhật ký nhị phân dựa trên câu lệnh thật tệ. Tôi thực sự mong chờ một tương lai mà tất cả chúng ta đều chuyển sang hàng ghi nhật ký nhị phân dựa trên (có sẵn từ MySQL 5.1, nhưng không được bật theo mặc định).
-
Với sao chép dựa trên hàng, nếu bạn thay đổi mức độ cách ly thành cam kết đọc, thì chỉ một hàng phù hợp cần được khóa.
-
Nếu bạn muốn trở thành một kẻ bạo dâm, bạn cũng có thể bật innodb_locks_unsafe_for_binlog với bản sao dựa trên câu lệnh.
Cập nhật ngày 22 tháng 4 :Để sao chép + dán phiên bản testcase đã cải tiến của tôi (nó không tìm kiếm 'trong khoảng trống'):
session1> CREATE TABLE test (id int not null primary key auto_increment, data1 int, data2 int, INDEX(data1)) engine=innodb;
Query OK, 0 rows affected (0.00 sec)
session1> INSERT INTO test VALUES (NULL, 1, 2), (NULL, 2, 1), (5, 2, 2), (6, 3, 3), (3, 3, 4), (4, 4, 3);
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0
session1> start transaction;
Query OK, 0 rows affected (0.00 sec)
session1> SELECT id FROM test ORDER BY data1 LIMIT 1 FOR UPDATE;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
session2> INSERT INTO test values (NULL, 0, 99); # blocks - 0 is in the gap between the lowest value found (1) and the "special" lowest value.
# At the same time, from information_schema:
localhost information_schema> select * from innodb_locks\G
*************************** 1. row ***************************
lock_id: 151A1C:1735:4:2
lock_trx_id: 151A1C
lock_mode: X,GAP
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
*************************** 2. row ***************************
lock_id: 151A1A:1735:4:2
lock_trx_id: 151A1A
lock_mode: X
lock_type: RECORD
lock_table: `so5694658`.`test`
lock_index: `data1`
lock_space: 1735
lock_page: 4
lock_rec: 2
lock_data: 1, 1
2 rows in set (0.00 sec)
# Another example:
select * from test where id < 1 for update; # blocks