MySQL: Giao dịch so với Bảng khóa


110

Tôi hơi bối rối với các giao dịch và khóa bảng để đảm bảo tính toàn vẹn của cơ sở dữ liệu và đảm bảo CHỌN và CẬP NHẬT vẫn đồng bộ và không có kết nối nào khác can thiệp vào nó. Tôi cần phải:

SELECT * FROM table WHERE (...) LIMIT 1

if (condition passes) {
   // Update row I got from the select 
   UPDATE table SET column = "value" WHERE (...)

   ... other logic (including INSERT some data) ...
}

Tôi cần đảm bảo rằng không có truy vấn nào khác sẽ can thiệp và thực hiện tương tự SELECT(đọc 'giá trị cũ' trước khi kết nối đó hoàn tất cập nhật hàng.

Tôi biết tôi có thể đặt mặc định LOCK TABLES tablelà chỉ đảm bảo rằng chỉ có 1 kết nối đang thực hiện việc này tại một thời điểm và mở khóa khi tôi hoàn tất, nhưng điều đó có vẻ quá mức cần thiết. Việc bao bọc điều đó trong một giao dịch có thực hiện điều tương tự không (đảm bảo không có kết nối nào khác cố gắng thực hiện cùng một quy trình trong khi một kết nối khác vẫn đang xử lý)? Hoặc sẽ là một SELECT ... FOR UPDATEhoặc SELECT ... LOCK IN SHARE MODEtốt hơn?

Câu trả lời:


173

Khóa bảng ngăn người dùng DB khác ảnh hưởng đến các hàng / bảng bạn đã khóa. Nhưng khóa, trong và của chính chúng, sẽ KHÔNG đảm bảo rằng logic của bạn xuất hiện ở trạng thái nhất quán.

Hãy nghĩ về một hệ thống ngân hàng. Khi bạn thanh toán hóa đơn trực tuyến, có ít nhất hai tài khoản bị ảnh hưởng bởi giao dịch: Tài khoản của bạn, nơi tiền được lấy. Và tài khoản của người nhận, nơi tiền được chuyển vào. Và tài khoản của ngân hàng, nơi họ sẽ vui vẻ gửi tất cả các khoản phí dịch vụ được tính vào giao dịch. Giả sử (như mọi người đều biết ngày nay) rằng các ngân hàng cực kỳ ngu ngốc, giả sử hệ thống của họ hoạt động như thế này:

$balance = "GET BALANCE FROM your ACCOUNT";
if ($balance < $amount_being_paid) {
    charge_huge_overdraft_fees();
}
$balance = $balance - $amount_being paid;
UPDATE your ACCOUNT SET BALANCE = $balance;

$balance = "GET BALANCE FROM receiver ACCOUNT"
charge_insane_transaction_fee();
$balance = $balance + $amount_being_paid
UPDATE receiver ACCOUNT SET BALANCE = $balance

Giờ đây, không có khóa và không có giao dịch, hệ thống này dễ bị ảnh hưởng bởi các điều kiện đua khác nhau, trong đó lớn nhất là nhiều khoản thanh toán được thực hiện trên tài khoản của bạn hoặc tài khoản của người nhận song song. Mặc dù mã của bạn đã được truy xuất số dư và đang thực hiện lệnh khổng lồ () và không thể thực hiện được, hoàn toàn có khả năng một số khoản thanh toán khác sẽ chạy song song cùng một loại mã. Họ sẽ truy xuất số dư của bạn (giả sử, 100 đô la), thực hiện các giao dịch của họ (lấy 20 đô la bạn đang thanh toán và 30 đô la mà họ đang siết chặt bạn) và bây giờ cả hai đường dẫn mã có hai số dư khác nhau: 80 đô la và 70 đô la. Tùy thuộc vào số tiền nào kết thúc cuối cùng, bạn sẽ nhận được một trong hai số dư đó trong tài khoản của mình, thay vì $ 50 mà lẽ ra bạn phải có ($ 100 - $ 20 - $ 30). Trong trường hợp này, "lỗi ngân hàng có lợi cho bạn"

Bây giờ, giả sử bạn sử dụng ổ khóa. Thanh toán hóa đơn của bạn (20 đô la) chạm vào đường ống đầu tiên, do đó, nó sẽ thắng và khóa hồ sơ tài khoản của bạn. Giờ đây, bạn đã có quyền sử dụng độc quyền và có thể khấu trừ 20 đô la từ số dư và ghi số dư mới trở lại bình yên ... và tài khoản của bạn kết thúc với 80 đô la như dự kiến. Nhưng ... uhh ... Bạn cố gắng cập nhật tài khoản của người nhận, tài khoản của người nhận bị khóa và bị khóa lâu hơn mã cho phép, làm hết thời gian thực hiện giao dịch của bạn ... Chúng tôi đang xử lý các ngân hàng ngu ngốc, vì vậy thay vì gặp lỗi thích hợp xử lý, mã chỉ kéo một exit()và 20 đô la của bạn biến mất thành một luồng điện tử. Bây giờ bạn đã hết 20 đô la và bạn vẫn nợ người nhận 20 đô la và điện thoại của bạn sẽ bị thu hồi.

Vì vậy, ... nhập giao dịch. Bạn bắt đầu một giao dịch, bạn ghi nợ tài khoản của mình 20 đô la, bạn cố gắng ghi có cho người nhận 20 đô la ... và một cái gì đó lại nổ tung. Nhưng lần này, thay vì exit(), mã có thể làm được rollback, và thật đáng tiếc, 20 đô la của bạn được thêm trở lại tài khoản của bạn một cách kỳ diệu.

Cuối cùng, nó kết thúc với điều này:

Các khóa ngăn không cho bất kỳ ai khác can thiệp vào bất kỳ bản ghi cơ sở dữ liệu nào mà bạn đang xử lý. Các giao dịch giữ cho mọi lỗi "sau" không can thiệp vào những việc "trước đó" bạn đã làm. Không một mình có thể đảm bảo rằng mọi thứ cuối cùng sẽ diễn ra tốt đẹp. Nhưng cùng nhau, họ làm.

trong bài học ngày mai: Niềm vui của Bế tắc.


4
Tôi cũng / vẫn đang bối rối. Giả sử tài khoản người nhận có 100 đô la để bắt đầu và chúng tôi đang thêm khoản thanh toán hóa đơn 20 đô la từ tài khoản của mình. Sự hiểu biết của tôi về các giao dịch là khi chúng bắt đầu, bất kỳ hoạt động trong giao dịch nào cũng nhìn thấy cơ sở dữ liệu ở trạng thái lúc bắt đầu giao dịch. nghĩa là: cho đến khi chúng tôi thay đổi nó, tài khoản người nhận có $ 100. Vì vậy, ... khi chúng tôi thêm 20 đô la, chúng tôi thực sự đặt số dư là 120 đô la. Nhưng điều gì sẽ xảy ra nếu trong quá trình giao dịch của chúng tôi, ai đó rút tài khoản người nhận xuống còn 0 đô la? Điều này được ngăn chặn bằng cách nào đó? Họ có nhận lại được 120 đô la một cách kỳ diệu không? Đây có phải là lý do tại sao ổ khóa cũng cần thiết?
Russ

Vâng, đó là lúc ổ khóa phát huy tác dụng. Một hệ thống thích hợp sẽ khóa hồ sơ để không ai khác có thể cập nhật hồ sơ trong khi giao dịch đang diễn ra. Một hệ thống hoang tưởng sẽ đặt một khóa vô điều kiện vào hồ sơ để không ai có thể đọc được số dư "cũ".
Marc B

1
Về cơ bản, hãy xem các giao dịch như bảo mật những thứ bên trong đường dẫn mã của bạn. Khóa những thứ an toàn trên các đường dẫn mã "song song". Cho đến khi bế tắc xảy ra ...
Marc B

1
@MarcB, Vậy tại sao chúng ta phải khóa một cách rõ ràng nếu chỉ sử dụng các giao dịch đã đảm bảo rằng các khóa được đặt đúng chỗ? Thậm chí sẽ có trường hợp theo đó chúng ta phải thực hiện khóa rõ ràng vì chỉ các giao dịch là không đủ?
Pacerier

2
Câu trả lời này không đúng và có thể dẫn đến kết luận sai. Tuyên bố này: "Các khóa ngăn không cho bất kỳ ai khác can thiệp vào bất kỳ bản ghi cơ sở dữ liệu nào mà bạn đang xử lý. Các giao dịch giữ cho mọi lỗi" sau "không can thiệp vào những việc" trước đó "mà bạn đã làm. Không một mình có thể đảm bảo rằng mọi thứ diễn ra bình thường trong kết thúc. Nhưng cùng nhau, họ làm. " - sẽ khiến bạn bị sa thải, điều đó cực kỳ sai lầm và ngu ngốc. Xem các bài viết: en.wikipedia.org/wiki/ACID , en.wikipedia.org/wiki/Isolation_(database_systems)dev.mysql.com/doc/refman/5.1/ vi /…
Nikola Svitlica

14

Bạn muốn một SELECT ... FOR UPDATEhoặc SELECT ... LOCK IN SHARE MODEbên trong một giao dịch, như bạn đã nói, vì thông thường các CHỌN, bất kể chúng có đang trong một giao dịch hay không, sẽ không khóa bảng. Cái nào bạn chọn sẽ tùy thuộc vào việc bạn có muốn các giao dịch khác có thể đọc hàng đó trong khi giao dịch của bạn đang diễn ra hay không.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOTsẽ không thực hiện thủ thuật cho bạn, vì các giao dịch khác vẫn có thể xuất hiện và sửa đổi hàng đó. Điều này được đề cập ngay ở đầu liên kết bên dưới.

Nếu các phiên khác đồng thời cập nhật cùng một bảng [...], bạn có thể thấy bảng ở trạng thái chưa từng tồn tại trong cơ sở dữ liệu.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consists-read.html


7

Các khái niệm giao dịch và khóa là khác nhau. Tuy nhiên, giao dịch đã sử dụng các khóa để giúp nó tuân theo các nguyên tắc ACID. Nếu bạn muốn ngăn người khác đọc / ghi tại cùng một thời điểm trong khi bạn đang đọc / ghi, bạn cần có một khóa để thực hiện việc này. Nếu bạn muốn đảm bảo tính toàn vẹn và nhất quán của dữ liệu, bạn đã có các giao dịch sử dụng tốt hơn. Tôi nghĩ rằng các khái niệm hỗn hợp về mức độ cô lập trong các giao dịch có khóa. Vui lòng tìm kiếm các cấp độ cô lập của các giao dịch, SERIALIZE phải là cấp độ bạn muốn.


Đây phải là câu trả lời chính xác. Khóa là để ngăn chặn các điều kiện cuộc đua và các giao dịch là để cập nhật nhiều bảng với dữ liệu phụ thuộc. Hai khái niệm hoàn toàn khác nhau, mặc dù các giao dịch sử dụng khóa.
Blue Water

6

Tôi đã gặp sự cố tương tự khi thử một IF NOT EXISTS ...và sau đó thực hiện một INSERTđiều đã gây ra tình trạng chạy đua khi nhiều luồng đang cập nhật cùng một bảng.

Tôi đã tìm thấy giải pháp cho vấn đề ở đây: Cách viết truy vấn CHÈN NẾU KHÔNG TỒN TẠI trong SQL chuẩn

Tôi nhận thấy điều này không trực tiếp trả lời câu hỏi của bạn nhưng nguyên tắc tương tự của việc thực hiện kiểm tra và chèn như một câu lệnh duy nhất rất hữu ích; bạn sẽ có thể sửa đổi nó để thực hiện cập nhật của mình.


2

Bạn đang nhầm lẫn với khóa & giao dịch. Họ là hai thứ khác nhau trong RMDB. Khóa ngăn chặn các hoạt động đồng thời trong khi giao dịch tập trung vào việc cô lập dữ liệu. Kiểm tra này bài viết tuyệt vời cho việc làm rõ và một số giải pháp duyên dáng.


1
Khóa ngăn người khác can thiệp vào các bản ghi mà bạn đang làm việc mô tả những gì nó làm một cách ngắn gọn và các giao dịch ngăn các lỗi sau đó (những người khác thực hiện thay đổi song song) can thiệp vào những việc bạn đã làm trước đó (bằng cách cho phép khôi phục trong trường hợp ai đó đã làm điều gì đó song song) khá nhiều tổng kết các giao dịch ... điều gì khiến anh ấy bối rối về khả năng hiểu các chủ đề này?
steviesama

1

Tôi sẽ sử dụng một

START TRANSACTION WITH CONSISTENT SNAPSHOT;

để bắt đầu, và

COMMIT;

để kết thúc với.

Bất cứ điều gì bạn làm ở giữa đều được cách ly với những người dùng khác trong cơ sở dữ liệu của bạn nếu công cụ lưu trữ của bạn hỗ trợ các giao dịch (là InnoDB).


1
Ngoại trừ bảng anh ấy đang chọn sẽ không bị khóa với các phiên khác trừ khi anh ấy khóa nó một cách cụ thể (hoặc cho đến khi CẬP NHẬT của anh ấy xảy ra), có nghĩa là các phiên khác có thể xuất hiện và sửa đổi nó giữa CHỌN và CẬP NHẬT.
Alison R.

Sau khi đọc phần BẮT ĐẦU GIAO DỊCH VỚI SNAPSHOT CÓ SỰ ĐỒNG Ý trong tài liệu MySQL, tôi không thấy nó thực sự khóa một kết nối khác cập nhật cùng một hàng ở đâu. Sự hiểu biết của tôi là nó sẽ thấy tuy nhiên bảng đã bắt đầu khi bắt đầu giao dịch. Vì vậy, nếu một giao dịch khác đang diễn ra, đã có một hàng và sắp cập nhật nó, thì giao dịch thứ 2 sẽ vẫn thấy hàng trước khi nó được cập nhật. Nó có thể có khả năng cố gắng cập nhật cùng một hàng mà giao dịch khác sắp thực hiện. Điều đó có chính xác hay tôi đang thiếu một cái gì đó trong quá trình?
Ryan

1
@Ryan Nó không thực hiện bất kỳ khóa nào; bạn nói đúng. Việc khóa (hay không) được xác định bởi loại thao tác bạn thực hiện (CHỌN / CẬP NHẬT / XÓA).
Alison R.

4
Tôi hiểu rồi. Nó cung cấp tính nhất quán đọc giao dịch của riêng bạn, nhưng không chặn người dùng khác sửa đổi một hàng ngay trước khi bạn làm.
Martin Schapendonk
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.