Cách thực hiện chính xác khóa lạc quan trong MySQL


11

Làm thế nào để thực hiện chính xác khóa lạc quan trong MySQL?

Nhóm của chúng tôi đã suy luận rằng chúng tôi phải làm số 4 dưới đây nếu không có nguy cơ rằng một luồng khác có thể cập nhật cùng một phiên bản của hồ sơ, nhưng chúng tôi muốn xác thực rằng đây là cách tốt nhất để làm điều đó.

  1. Tạo trường phiên bản trên bảng bạn muốn sử dụng khóa tối ưu cho ví dụ tên cột = "phiên bản"
  2. Khi chọn, đảm bảo bao gồm cột phiên bản và ghi chú của phiên bản
  3. Trong lần cập nhật tiếp theo cho bản ghi, câu lệnh cập nhật sẽ phát hành "where version = X" trong đó X là phiên bản chúng tôi nhận được ở # 2 và đặt trường phiên bản trong câu lệnh cập nhật đó thành X + 1
  4. Thực hiện một SELECT FOR UPDATEbản ghi mà chúng tôi sẽ cập nhật để chúng tôi tuần tự hóa những người có thể thay đổi bản ghi mà chúng tôi đang cố gắng cập nhật.

Để làm rõ, chúng tôi đang cố gắng ngăn hai luồng chọn cùng một bản ghi trong cùng một cửa sổ trong đó chúng lấy cùng một phiên bản của bản ghi để ghi đè lên nhau nếu chúng cố gắng cập nhật bản ghi cùng một lúc. Chúng tôi tin rằng trừ khi chúng tôi làm số 4, có một cơ hội, nếu cả hai luồng nhập giao dịch tương ứng của họ cùng một lúc (nhưng chưa đưa ra bản cập nhật của họ), khi họ đi cập nhật, luồng thứ hai sẽ sử dụng CẬP NHẬT ... trong đó phiên bản = X sẽ hoạt động trên dữ liệu cũ.

Chúng ta có đúng khi nghĩ rằng chúng ta phải thực hiện khóa bi quan này khi cập nhật mặc dù chúng ta đang sử dụng các trường phiên bản / khóa lạc quan?


Có vấn đề gì vậy? Bạn tăng số phiên bản với CẬP NHẬT của mình, sau đó CẬP NHẬT thứ hai sẽ thất bại vì số phiên bản không giống như khi đọc - đó là những gì bạn muốn.
AndreKR

Bạn chắc chứ? Không rõ ràng trừ khi bạn đặt mức cô lập giao dịch thành một cài đặt cụ thể mà bạn thực sự sẽ thấy các luồng khác được cập nhật. Nếu cả hai bạn tham gia giao dịch cùng một lúc, luồng thứ hai rất có thể thấy dữ liệu OLD khi thực hiện cập nhật. MySQL không mạnh mẽ trong lĩnh vực ACID như Oracle nói, do đó tìm kiếm cách thực hành tốt nhất để thực hiện khóa lạc quan trong MySQL sẽ ngăn chặn việc đọc / cập nhật bẩn.
BestPractices

Nhưng sau đó giao dịch sẽ thất bại trong quá trình cam kết, phải không?
AndreKR

Dấu hiệu cho thấy người ta muốn thực hiện lựa chọn cập nhật để xử lý tình huống này: dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html
BestPractices

@BestPractices Bạn cần một trong hai SELECT ... FOR UPDATE hoặc khóa lạc quan bởi hàng versioning, không phải cả hai. Xem chi tiết trong câu trả lời.
Craig Ringer

Câu trả lời:


15

Nhà phát triển của bạn bị nhầm. Bạn cần một trong hai SELECT ... FOR UPDATE hoặc hàng versioning, không phải cả hai.

Hãy thử nó và xem. Mở ba phiên MySQL (A), (B)(C)cơ sở dữ liệu tương tự.

Trong (C)vấn đề:

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;

Trong cả hai (A)(B)đưa ra một UPDATEthử nghiệm và thiết lập phiên bản hàng, thay đổi winnervăn bản trong mỗi phiên bản để bạn có thể xem phiên nào là phiên nào:

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

Bây giờ trong (C), UNLOCK TABLES;để phát hành khóa.

(A)(B)sẽ chạy đua cho khóa hàng. Một trong số họ sẽ giành chiến thắng và có được khóa. Cái khác sẽ chặn trên khóa. Người chiến thắng đã nhận được khóa sẽ tiến hành thay đổi hàng. Giả sử (A)là người chiến thắng, bây giờ bạn có thể thấy hàng đã thay đổi (vẫn chưa được cam kết nên không hiển thị cho các giao dịch khác) với a SELECT * FROM test WHERE id = 1.

Bây giờ COMMITtrong phiên chiến thắng, nói (A).

(B)sẽ nhận được khóa và tiến hành cập nhật. Tuy nhiên, phiên bản không còn phù hợp, do đó, nó sẽ thay đổi không có hàng, như được báo cáo bởi kết quả đếm hàng. Chỉ có một UPDATEhiệu ứng và ứng dụng khách có thể thấy rõ cái nào UPDATEthành công và cái nào thất bại. Không cần khóa thêm.

Xem nhật ký phiên tại pastebin tại đây . Tôi đã sử dụng mysql --prompt="A> "vv để làm cho nó dễ dàng để nói sự khác biệt giữa các phiên. Tôi đã sao chép và dán đầu ra xen kẽ theo trình tự thời gian, vì vậy nó không hoàn toàn là đầu ra thô và có thể tôi đã mắc lỗi sao chép và dán nó. Tự mình kiểm tra xem.


Nếu bạn chưa thêm trường phiên bản hàng, thì bạn sẽ cần phải SELECT ... FOR UPDATEcó khả năng đảm bảo đặt hàng một cách đáng tin cậy.

Nếu bạn nghĩ về nó, SELECT ... FOR UPDATEthì hoàn toàn dư thừa nếu bạn ngay lập tức thực hiện UPDATEmà không sử dụng lại dữ liệu từ SELECThoặc nếu bạn đang sử dụng phiên bản hàng. Dù sao UPDATEcũng sẽ có một khóa. Nếu người khác cập nhật hàng giữa lần đọc và lần viết tiếp theo của bạn, phiên bản của bạn sẽ không khớp nữa nên bản cập nhật của bạn sẽ thất bại. Đó là cách khóa lạc quan hoạt động.

Mục đích của SELECT ... FOR UPDATElà:

  • Để quản lý trật tự khóa để tránh bế tắc; và
  • Để mở rộng khoảng thời gian khóa hàng khi bạn muốn đọc dữ liệu từ một hàng, hãy thay đổi nó trong ứng dụng và viết một hàng mới dựa trên bản gốc mà không phải sử dụng SERIALIZABLEcách ly hoặc phiên bản hàng.

Bạn không cần sử dụng cả khóa tối ưu (phiên bản hàng) và SELECT ... FOR UPDATE. Sử dụng cái này hay cái khác


Cảm ơn Craig. Bạn đã đúng-- nhà phát triển đã nhầm. Cảm ơn đã chạy thử nghiệm này.
BestPractices

Còn máy chủ SQL thì sao? Có luôn có một khóa có được trên hàng cập nhật độc lập với mức cô lập giao dịch không?
plalx

@plalx Vâng, tài liệu nói gì? Điều gì xảy ra nếu bạn chạy thử nghiệm tương tác giống như thử nghiệm này?
Craig Ringer

@CraigRinger, điều gì sẽ xảy ra nếu B nhận được khóa trước khi A cam kết nhưng sau khi cập nhật A?
MạnhT

1
@MengT Không thể, đó là lý do tại sao nó là một khóa.
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;

Không cần Khóa (không phải bảng, không giao dịch) hoặc thậm chí mong muốn:

  • CẬP NHẬT là nguyên tử
  • LAST_INSERT_ID () là phiên cụ thể, do đó an toàn cho chuỗi.
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.