Chuyển đến nội dung chính
Mô-đun 11 · Bài học 2 trong tổng số 442/57 trong khóa học~10 min
Bài học theo mô-đun (2/4)

Điều kiện cuộc đua

Việc hiểu cách cơ sở dữ liệu hoạt động độc lập trên một thiết bị đầu cuối rất đơn giản, nhưng trong kịch bản web thực tế sẽ có hàng trăm quy trình "Node/PHP/Go" đồng thời gửi yêu cầu. Đây là nơi ra đời các Điều kiện Cuộc đua nổi tiếng.

Vấn đề "Mất cập nhật"

Mất Cập nhật xảy ra khi hai luồng đọc cùng một hàng cũ, thực hiện một số phép toán trên máy chủ JavaScript và sau đó gửi lại CẬP NHẬT để lưu kết quả.

Kịch bản:

  • Sản phẩm số 5 có 10 chiếc.
  • Người dùng A mua 1 (Máy chủ A đọc “10”, tính “9”).
  • Trong cùng một phần nghìn giây, người dùng B mua một cái khác trước khi giao dịch mua của A kết thúc giai đoạn CAM KẾT.
  • Server B cũng đọc “10”, tính “9”.
  • Máy chủ A gửi UPDATE: set quantity = 9.
  • Máy chủ B gửi UPDATE: set quantity = 9.
  • Cả hai đều cho rằng mình đã giảm số lượng thành công. Nhưng giá trị cuối cùng lẽ ra phải là 8 đơn vị! Một giao dịch mua ảo đã bị mất.

Giải pháp không có giao dịch lớn (Cập nhật nguyên tử)

Thay vì đọc và làm toán bên ngoài, hãy để DB thực hiện việc đó trong quá trình cập nhật, quá trình này luôn được đồng bộ hóa.

SQL
-- Wrong
UPDATE products SET stock = 9 WHERE id = 5;

-- Correct and natively thread-safe
UPDATE products SET stock = stock - 1 WHERE id = 5;

Nhiều dị thường nghiêm trọng hơn (Đọc bẩn & Phantoms)

  • Đọc bẩn: Bạn đọc dữ liệu tạm thời của người khác chưa được CAM KẾT xác nhận (và có thể bất ngờ chuyển sang ROLLBACK khiến bạn vô hiệu).
  • Đọc ảo: Bạn quét tất cả các hàng, trong khi luồng thứ hai chèn một bản ghi mới, sau đó bạn thực hiện một lần quét khác trong cùng một giao dịch và bạn phát hiện ra một cách thần kỳ rằng bản ghi mới xuất hiện bất ngờ, làm ảnh hưởng đến các báo cáo tài chính đã được tính toán.
tập thể dục#sql.m11.l2.e1
Nỗ lực: 0Đang tải…

Sửa đổi nguyên bản bản ghi khách hàng để thực hiện một thao tác chuỗi nhỏ.\nGiả sử chúng ta muốn đổi tên 'thành phố' của khách hàng để thêm dấu hoa thị. \nThực hiện điều đó bằng cách tận dụng giá trị riêng của hàng cho hàng có id=10, nối ' - Cũ' với giá trị hiện tại (Sử dụng ống nối postgres' ||).

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

CẬP NHẬT khách hàng SET city = city || ' - Cũ' WHERE id = 10;

Giải pháp khả dụng sau 3 lần thử

tập thể dục#sql.m11.l2.e2
Nỗ lực: 0Đang tải…

Một bản cập nhật cổ điển bị mất xảy ra trong các khoản thanh toán và giá cả.\nGiả sử có đợt tăng giá khẩn cấp: Tăng 'unit_price' của bảng 'order_items' (đối với sản phẩm 1 của đơn hàng 1) lên 20% theo nguyên tử, tức là nhân nó * 1,2 thay vì chèn giá mới được tính trước theo cách thủ công trong JS.

Đang tải trình chỉnh sửa…
Hiển thị gợi ý

CẬP NHẬT order_items SET unit_price = unit_price * 1.2 Ở ĐÂU order_id = 1 VÀ Product_id = 1;

Giải pháp khả dụng sau 3 lần thử