2016-11-22 22 views
5

Tôi phát triển một hệ thống đặt chỗ trực tuyến. Để đơn giản hóa, giả sử người dùng có thể đặt nhiều mục và mỗi mục chỉ có thể được đặt một lần. Các mục được thêm vào giỏ hàng đầu tiên.Cách sử dụng đúng các giao dịch và khóa để đảm bảo tính toàn vẹn của cơ sở dữ liệu?

Ứng dụng sử dụng MySql/InnoDB cơ sở dữ liệu. Theo tài liệu MySql, mức cô lập mặc định là Repeatable reads.

Dưới đây là thủ tục thanh toán tôi đã đưa ra cho đến nay:

  1. Bắt đầu giao dịch
  2. Chọn các mục trong giỏ hàng mua sắm (với for update khóa)
    ghi từ cart-itemitems bảng được tìm nạp ở bước này.
  3. Kiểm tra xem các mặt hàng chưa được đặt bởi bất kỳ ai khác
    Kiểm tra cơ bản nếu quantity > 0. Nó phức tạp hơn trong ứng dụng thực tế, do đó tôi đặt nó ở đây như một bước riêng biệt.
  4. Cập nhật các mục, đặt quantity = 0
    Cũng thực hiện các thao tác cơ sở dữ liệu cần thiết khác.
  5. Thanh toán (qua api bên ngoài như PayPal hoặc sọc)
    Không cần tương tác với người dùng vì chi tiết thanh toán có thể được thu trước khi thanh toán.
  6. Nếu mọi thứ diễn ra tốt đẹp cam kết giao dịch hoặc rollback khác
  7. Tiếp tục với logic không cần thiết
    Gửi e-mail vv trong trường hợp thành công, chuyển hướng cho lỗi.

Tôi không chắc liệu điều đó có đủ hay không. Tôi lo lắng liệu:

  1. Người dùng khác cố gắng đặt cùng một mục cùng một lúc sẽ được xử lý đúng. Giao dịch của anh ta T2 có chờ đến khi T1 được thực hiện không?
  2. Thanh toán bằng PayPal hoặc Stripe có thể mất chút thời gian. Điều này sẽ không trở thành vấn đề về hiệu suất?
  3. Tính khả dụng của các mục sẽ được hiển thị chính xác mọi lúc (các mục sẽ khả dụng cho đến khi thanh toán thành công). Những lựa chọn chỉ đọc này có sử dụng shared lock không?
  4. Có thể MySql rollbacks tự giao dịch không? Có tốt hơn là thử lại tự động hoặc hiển thị thông báo lỗi và cho phép người dùng thử lại không?
  5. Tôi đoán là đủ nếu tôi làm SELECT ... FOR UPDATE trên bảng items. Bằng cách này, cả hai yêu cầu gây ra bởi nhấp đúp chuột và người dùng khác sẽ phải chờ cho đến khi giao dịch kết thúc. Họ sẽ chờ đợi vì họ cũng sử dụng FOR UPDATE. Trong khi đó, vanilla SELECT sẽ chỉ nhìn thấy ảnh chụp nhanh của db trước giao dịch, mặc dù không có sự chậm trễ, phải không?
  6. Nếu tôi sử dụng JOIN trong SELECT ... FOR UPDATE, sẽ ghi lại trong cả hai bảng sẽ bị khóa?
  7. Tôi hơi bối rối về CHỌN ... CẬP NHẬT trên các hàng không tồn tại phần câu trả lời của Willem Renzema. Khi nào nó có thể trở nên quan trọng? Bạn có thể cung cấp bất kỳ ví dụ nào không?

Dưới đây là một số tài nguyên Tôi đã đọc: How to deal with concurrent updates in databases?, MySQL: Transactions vs Locking Tables, Do database transactions prevent race conditions?, Isolation (database systems), InnoDB Locking and Transaction Model, A beginner’s guide to database locking and the lost update phenomena.

Viết lại câu hỏi ban đầu của tôi để làm cho câu hỏi tổng quát hơn.
Đã thêm câu hỏi tiếp theo.

+0

Câu hỏi quan trọng: Khi bạn 'CHỌN ... ĐỂ CẬP NHẬT ', bạn có đang chọn một hàng bạn biết đã tồn tại hay không, nơi bạn định chèn hàng? –

+0

@WillemRenzema Tôi chỉ chọn các hàng đã tồn tại thông qua bảng tổng hợp 'cart-item' – Paul

Trả lời

1

1. Người dùng khác cố gắng đặt cùng một mục cùng một lúc sẽ được xử lý correcly. Giao dịch của anh ta T2 có chờ đến khi T1 được thực hiện không?

Có. Trong khi giao dịch đang hoạt động giữ FOR UPDATE khóa trên hồ sơ, các phát biểu trong các giao dịch khác sử dụng bất kỳ khóa nào (SELECT ... FOR UPDATE, , DELETE) sẽ bị tạm ngưng cho đến khi giao dịch hoạt động hoặc "Thời gian chờ khóa" vượt quá.

2. Thanh toán bằng PayPal hoặc Stripe có thể mất chút thời gian. Điều này sẽ không trở thành vấn đề về hiệu suất?

Điều này sẽ không thành vấn đề vì đây là chính xác những gì cần thiết. Các giao dịch thanh toán nên được thực hiện tuần tự, tức là. kiểm tra sau không nên bắt đầu trước khi kết thúc trước đây.

3. Tính khả dụng của các mục sẽ được hiển thị chính xác mọi lúc (các mục sẽ khả dụng cho đến khi thanh toán thành công). Những lựa chọn chỉ đọc này có sử dụng shared lock không?

Repeatable reads mức cách ly đảm bảo rằng các thay đổi được thực hiện bởi giao dịch sẽ không hiển thị cho đến khi giao dịch đó được thực hiện. Do đó các mục khả dụng sẽ được hiển thị chính xác. Không có gì sẽ được hiển thị không có sẵn trước khi nó thực sự được trả tiền. Không cần khóa.

SELECT ... LOCK IN SHARE MODE sẽ khiến giao dịch thanh toán phải chờ cho đến khi hoàn tất. Điều này có thể làm chậm quá trình kiểm tra mà không phải trả tiền.

4. Có thể MySql rollbacks giao dịch của chính nó? Có tốt hơn là thử lại tự động hoặc hiển thị thông báo lỗi và cho phép người dùng thử lại không?

Có thể. Giao dịch có thể được khôi phục khi "Khóa chờ thời gian chờ" vượt quá hoặc khi bế tắc xảy ra. Trong trường hợp đó, bạn nên thử lại tự động.
Theo các tuyên bố bị treo mặc định không thành công sau 50 giây.

5. Tôi đoán là đủ nếu tôi làm SELECT ... FOR UPDATE trên bảng items. Bằng cách này, cả hai yêu cầu gây ra bởi nhấp đúp chuột và người dùng khác sẽ phải chờ cho đến khi giao dịch kết thúc. Họ sẽ chờ đợi vì họ cũng sử dụng FOR UPDATE. Trong khi đó, vanilla SELECT sẽ chỉ nhìn thấy ảnh chụp nhanh của db trước giao dịch, mặc dù không có sự chậm trễ, phải không?

Có, SELECT ... FOR UPDATE trên items bảng là đủ.
Có, các lựa chọn này chờ, vì FOR UPDATE là khóa độc quyền.
Có, đơn giản SELECT sẽ chỉ lấy giá trị như trước khi giao dịch bắt đầu, điều này sẽ xảy ra ngay lập tức.

6. Nếu tôi sử dụng JOIN trong SELECT ... FOR UPDATE, sẽ ghi lại trong cả hai bảng sẽ bị khóa?

Vâng, SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE, DELETE khóa tất cả các hồ sơ đọc, vì vậy bất cứ điều gì chúng tôi JOIN được bao gồm. Xem MySql Docs.

Điều thú vị (ít nhất là đối với tôi) mọi thứ được quét trong quá trình xử lý câu lệnh SQL bị khóa, cho dù nó được chọn hay không. Ví dụ: WHERE id < 10 cũng sẽ khóa bản ghi với id = 10!

Nếu bạn không có chỉ mục nào phù hợp với câu lệnh của bạn và MySQL phải quét toàn bộ bảng để xử lý câu lệnh, mọi hàng trong bảng sẽ bị khóa. Điều quan trọng là tạo các chỉ mục tốt để các truy vấn của bạn không cần quét nhiều hàng một cách không cần thiết.

4
  1. Bắt đầu giao dịch
  2. Chọn các mục trong giỏ hàng (với để cập nhật khóa)

Cho đến nay rất tốt, điều này ít nhất sẽ ngăn chặn người dùng từ làm thanh toán trong nhiều phiên (nhiều lần cố gắng để kiểm tra cùng một thẻ - tốt để đối phó với nhấp đúp chuột.)

  1. Kiểm tra xem có mục nào không được đặt bởi người dùng khác

Làm thế nào để bạn kiểm tra? Với tiêu chuẩn SELECT hoặc với SELECT ... FOR UPDATE? Dựa trên bước 5, tôi đoán bạn đang kiểm tra cột được đặt trước trên mục hoặc tương tự.

Vấn đề ở đây là SELECT ... FOR UPDATE ở bước 2 sẽ KHÔNG áp dụng khóa FOR UPDATE cho mọi thứ khác. Nó chỉ áp dụng cho những gì là SELECT ed: bảng cart-item. Dựa trên tên, đó sẽ là một kỷ lục khác nhau cho mỗi giỏ hàng/người dùng. Điều này có nghĩa là các giao dịch khác sẽ KHÔNG bị chặn khỏi việc tiếp tục.

  1. Thanh toán
  2. Cập nhật mục đánh dấu chúng như reserved
  3. Nếu mọi thứ diễn ra tốt đẹp cam kết giao dịch, rollback khác

Tiếp nối ở trên, dựa trên thông tin bạn đã cung cấp, bạn có thể kết thúc với nhiều người mua cùng một mặt hàng, nếu bạn không sử dụng SELECT ... FOR UPDATE trên bước 3.

đề nghị giải pháp

  1. Bắt đầu giao dịch
  2. SELECT ... FOR UPDATE bảng cart-item.

Điều này sẽ khóa nhấp đúp khi chạy. Những gì bạn chọn ở đây phải là một số loại cột "đặt hàng được đặt hàng". Nếu bạn làm điều này, một giao dịch thứ hai sẽ tạm dừng ở đây và chờ cho người đầu tiên kết thúc, và sau đó đọc kết quả đầu tiên được lưu vào cơ sở dữ liệu.

Đảm bảo kết thúc quá trình thanh toán tại đây nếu bảng cart-item cho biết đã được đặt hàng.

  1. SELECT ... FOR UPDATE bảng nơi bạn ghi lại nếu một mục đã được đặt trước.

Điều này sẽ khóa các xe/người dùng khác không thể đọc các mặt hàng đó.

Dựa trên kết quả, nếu mục này không dành riêng, tiếp tục:

  1. UPDATE ... bảng trong bước 3, đánh dấu các mục như reserved. Bạn cũng cần thực hiện bất kỳ số điện thoại nào khác INSERT s và UPDATE.

  2. Thanh toán. Phát hành khôi phục nếu dịch vụ thanh toán cho biết thanh toán không hoạt động.

  3. Ghi lại thanh toán, nếu thành công.

  4. Cam kết giao dịch

Hãy chắc chắn rằng bạn không làm bất cứ điều gì có thể thất bại giữa các bước 5 và 7 (như gửi email), nếu không bạn có thể kết thúc với họ thực hiện thanh toán mà không có nó được ghi lại , trong trường hợp giao dịch được khôi phục.

Bước 3 là bước quan trọng liên quan đến việc đảm bảo hai (hoặc hơn) người không cố gắng đặt hàng cùng một mục. Nếu hai người cố gắng, người thứ hai sẽ kết thúc trang web của họ "treo" trong khi nó xử lý trang đầu tiên. Sau đó, khi lần đầu tiên kết thúc, lần thứ 2 sẽ đọc cột "được bảo lưu" và bạn có thể trả lại tin nhắn cho người dùng rằng ai đó đã mua mặt hàng đó.

Thanh toán trong giao dịch hoặc không

Đây là chủ quan. Nói chung, bạn muốn đóng các giao dịch càng nhanh càng tốt, để tránh nhiều người bị khóa khỏi tương tác với cơ sở dữ liệu cùng một lúc.

Tuy nhiên, trong trường hợp này, bạn thực sự muốn họ đợi. Nó chỉ là vấn đề trong bao lâu.

Nếu bạn chọn thực hiện giao dịch trước khi thanh toán, bạn sẽ cần ghi lại tiến trình của mình trong một số bảng trung gian, chạy thanh toán và sau đó ghi lại kết quả. Xin lưu ý rằng nếu thanh toán không thành công, bạn sẽ phải hoàn tác thủ công các bản ghi đặt trước của mục mà bạn đã cập nhật.

SELECT ... FOR UPDATE trên các hàng không tồn tại

Chỉ cần một lời cảnh báo, trong trường hợp thiết kế bảng của bạn liên quan đến việc chèn hàng nơi bạn cần phải sớm SELECT ... FOR UPDATE: Nếu một hàng không tồn tại, giao dịch đó sẽ KHÔNG làm cho các giao dịch khác chờ đợi, nếu chúng cũng là SELECT ... FOR UPDATE cùng một hàng không tồn tại.

Vì vậy, hãy đảm bảo luôn luôn sắp xếp các yêu cầu của bạn bằng cách thực hiện một số SELECT ... FOR UPDATE liên tiếp mà bạn biết trước tiên. Sau đó, bạn có thể SELECT ... FOR UPDATE trên hàng có thể có hoặc chưa tồn tại. (Đừng cố gắng làm chỉ một SELECT trên hàng có thể hoặc không tồn tại, vì bạn sẽ đọc trạng thái của hàng tại thời điểm giao dịch bắt đầu, không phải tại thời điểm bạn chạy SELECT. Vì vậy, SELECT ... FOR UPDATE trên các hàng không tồn tại vẫn là điều bạn cần làm để có được thông tin cập nhật nhất, chỉ cần lưu ý rằng nó sẽ không khiến các giao dịch khác phải đợi.)

+0

Cảm ơn câu trả lời chi tiết như vậy! Bạn đã thực hiện một điểm tuyệt vời mà tôi nên tránh bất kỳ mã nào có thể thất bại giữa thanh toán và cam kết. Vì vậy, tôi đã thay đổi thứ tự của các bước 4 và 5. Tôi nghĩ rằng tôi sẽ để lại thanh toán bên trong giao dịch, nếu không nó có thể xảy ra để thanh toán cho 'Người dùng 2' bị từ chối, nhưng sau một thời gian" đưa "mục lại xuất hiện, bởi vì' Người dùng 1' chưa bao giờ hoàn tất thanh toán. Tôi đã cập nhật bài đăng của mình để làm rõ các bước và thêm một số câu hỏi tiếp theo, hãy xem! – Paul

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