2010-11-19 35 views
84

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

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 phải đảm bảo rằng không truy vấn khác sẽ can thiệp và thực hiện cùng SELECT (đọc 'giá trị cũ' trước khi kết nối đó kết thúc việc cập nhật hàng

Tôi biết tôi có thể mặc định. đến LOCK TABLES table để đả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ẻ như quá mức. trong khi quá trình khác vẫn đang được xử lý)? Hoặc có thể là SELECT ... FOR UPDATE hoặc SELECT ... LOCK IN SHARE MODE tốt hơn không?

Trả lời

124

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

Hãy nghĩ đến 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, từ đó tiền được thực hiện. Và tài khoản của người nhận, trong đó tiền được chuyển. Và tài khoản của ngân hàng, mà họ sẽ vui vẻ gửi tất cả các khoản phí dịch vụ tính trên giao dịch. Given (như mọi người đều biết những ngày này) mà các ngân hàng là vô cùng 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 

Bây giờ, không có ổ khóa và không có giao dịch, hệ thống này là dễ bị tổn thương với điều kiện chủng tộc khác nhau, lớn nhất của đó 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. Trong khi mã của bạn có số dư được truy lục và đang thực hiện lệnh huge_overdraft_fees() và không có điều gì, thì hoàn toàn có thể một số thanh toán khác sẽ chạy cùng một loại mã song song. Họ sẽ lấy lại số dư của bạn (ví dụ 100 đô la), thực hiện giao dịch của họ (lấy ra 20 đô la bạn đang thanh toán và 30 đô la họ đang sử dụng), và bây giờ cả hai đường dẫn mã đều có hai số dư khác nhau: 80 đô la và $ 70. Tùy thuộc vào những người kết thúc cuối cùng, bạn sẽ kết thúc với một trong hai số dư trong tài khoản của bạn, thay vì $ 50 bạn nên đã kết thúc với ($ 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) lần đầu tiên truy cập vào đường ống, vì vậy nó thắng và khóa hồ sơ tài khoản của bạn. Bây giờ bạn đã có độc quyền sử dụng, và có thể khấu trừ $ 20 từ số dư, và viết số dư mới trở lại trong hòa bình ... và tài khoản của bạn kết thúc với $ 80 như dự kiến. Nhưng ... uhoh ... Bạn cố gắng cập nhật tài khoản của người nhận, và nó bị khóa, và khóa lâu hơn mã cho phép, định thời gian 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ì có lỗi thích hợp xử lý, mã chỉ kéo một exit() và $ 20 của bạn biến mất thành một loạt các electron. Bây giờ bạn đã hết 20 đô la và bạn vẫn còn nợ 20 đô la cho người nhận và điện thoại của bạn bị thu hồi.

Vì vậy, hã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 bạn 20 đô la, bạn cố gắng ghi có cho người nhận với số tiền $ 20 ... và một cái gì đó sẽ lại phát sinh. Nhưng lần này, thay vì exit(), mã chỉ có thể thực hiện rollback và poof, $ 20 của bạn được thêm lại vào tài khoản của bạn một cách kỳ diệu.

Cuối cùng, nó tóm gọn xuống điều này:

Khóa giữ 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 bạn đang xử lý. Giao dịch giữ bất kỳ lỗi "sau này" nào gây trở ngại cho những điều "trước đó" bạn đã thực hiện. Không một mình có thể đảm bảo rằng mọi thứ hoạt động tốt vào cuối cùng. Nhưng cùng nhau, họ làm.

trong bài học ngày mai: Niềm vui của Deadlocks.

+2

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

+0

Vâng, đó là nơi ổ khóa đi vào hoạt động. Một hệ thống thích hợp sẽ ghi-khóa bản ghi để không ai khác có thể cập nhật bản ghi 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ự cân bằng "cũ". –

+1

Về cơ bản, hãy xem xét các giao dịch như đảm bảo mọi 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 deadlocks trúng ... –

0

Tôi muốn sử dụng một

START TRANSACTION WITH CONSISTENT SNAPSHOT; 

để bắt đầu với, và một

COMMIT; 

để kết thúc bằng.

Mọi thứ bạn làm ở giữa được phân lập từ 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ợ giao dịch (tức là InnoDB).

+1

Ngoại trừ bảng mà anh ấy chọn sẽ không bị khóa cho các phiên khác trừ khi anh ấy khóa nó (hoặc cho đến khi UPDATE của anh ấy xảy ra), có nghĩa là các phiên khác có thể đi kèm và sửa đổi nó giữa SELECT và UPDATE. –

+0

Sau khi đọc trên START GIAO DỊCH VỚI SNAPSHOT AN TOÀN trong tài liệu MySQL, tôi không thấy nơi nó thực sự khóa một kết nối khác từ việc cập nhật cùng một hàng. Sự hiểu biết của tôi là nó sẽ thấy tuy nhiên bảng bắt đầu vào đầu giao dịch. Vì vậy, nếu một giao dịch khác đang được tiến hành, đã nhận được một hàng và sắp cập nhật nó, 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 giao dịch khác là về. Điều đó đúng hay tôi đang thiếu một cái gì đó trong tiến trình? – Ryan

+1

@Ryan Nó không thực hiện bất kỳ khóa nào; bạn nói đúng. Khóa (hoặc không) được xác định bởi loại hoạt động bạn làm (SELECT/UPDATE/DELETE). –

6

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

tôi tìm thấy giải pháp cho các vấn đề ở đây: How to write INSERT IF NOT EXISTS queries in standard SQL

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

+1

+1 cho bài viết được liên kết tuyệt vời là nền tảng độc lập. – Russ

13

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

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

START TRANSACTION WITH CONSISTENT SNAPSHOT sẽ không làm các trick cho bạn, như các giao dịch khác vẫn có thể đi cùng 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ể xem bảng ở trong tình trạng mà không bao giờ tồn tại trong cơ sở dữ liệu.

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

2

Bạn đang nhầm lẫn với giao dịch khóa &. Chúng 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 tách biệt dữ liệu. Kiểm tra this bài viết tuyệt vời để làm rõ và một số giải pháp duyên dáng.

+0

Khóa ngăn người khác can thiệp vào hồ sơ bạn đang làm việc với mô tả ngắn gọn và giao dịch ngăn chặn lỗi sau này (những người khác thay đổi song song) can thiệp vào những điều trước đó bạn đã thực hiện (bằng cách cho phép quay lại trong trường hợp ai đó đã làm) một cái gì đó song song) khá nhiều tiền lên giao dịch ... những gì là nhầm lẫn về hiểu của mình về các chủ đề này? – steviesama

2

Các khái niệm và khóa giao dịch khác nhau. Tuy nhiên, giao dịch sử dụng khóa để giúp nó tuân theo các nguyên tắc ACID. Nếu bạn muốn bàn để ngăn người khác đọc/ghi tại cùng một thời điểm trong khi bạn đọc/ghi, bạn cần 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 đã sử dụng giao dịch tốt hơn. Tôi nghĩ 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 mức cô lập các giao dịch, SERIALIZE phải là cấp bạn muốn.

Các vấn đề liên quan