Cả Mat và Erwin đều đúng, và tôi chỉ thêm một câu trả lời khác để mở rộng hơn nữa về những gì họ nói theo cách không phù hợp trong một bình luận. Vì câu trả lời của họ dường như không làm hài lòng tất cả mọi người, và có một gợi ý rằng các nhà phát triển PostgreQuery nên được tư vấn, và tôi là một, tôi sẽ giải thích.
Điểm quan trọng ở đây là theo tiêu chuẩn SQL, trong một giao dịch chạy ở READ COMMITTED
mức cô lập giao dịch, hạn chế là không thể nhìn thấy công việc của các giao dịch không được cam kết. Khi công việc của các giao dịch cam kết trở nên hữu hình là phụ thuộc vào việc thực hiện. Những gì bạn đang chỉ ra là một sự khác biệt trong cách hai sản phẩm đã chọn để thực hiện điều đó. Không thực hiện là vi phạm các yêu cầu của tiêu chuẩn.
Dưới đây là những gì xảy ra trong PostgreSQL, chi tiết:
S1-1 chạy (đã xóa 1 hàng)
Hàng cũ được đặt đúng vị trí, vì S1 vẫn có thể quay lại, nhưng S1 hiện giữ khóa trên hàng để bất kỳ phiên nào khác cố gắng sửa đổi hàng sẽ chờ xem liệu S1 có cam kết hay quay lại không. Bất kỳ lần đọc nào của bảng vẫn có thể thấy hàng cũ, trừ khi họ cố gắng khóa nó bằng SELECT FOR UPDATE
hoặc SELECT FOR SHARE
.
S2-1 chạy (nhưng bị chặn do S1 có khóa ghi)
Bây giờ S2 phải chờ xem kết quả của S1. Nếu S1 quay lại thay vì cam kết, S2 sẽ xóa hàng. Lưu ý rằng nếu S1 chèn một phiên bản mới trước khi quay trở lại, phiên bản mới sẽ không bao giờ tồn tại ở góc độ của bất kỳ giao dịch nào khác, phiên bản cũ cũng sẽ không bị xóa khỏi quan điểm của bất kỳ giao dịch nào khác.
S1-2 chạy (chèn 1 hàng)
Hàng này độc lập với hàng cũ. Nếu đã có bản cập nhật của hàng với id = 1, phiên bản cũ và mới sẽ có liên quan và S2 có thể xóa phiên bản cập nhật của hàng khi nó bị bỏ chặn. Rằng một hàng mới xảy ra có cùng giá trị với một số hàng tồn tại trong quá khứ không làm cho nó giống như một phiên bản cập nhật của hàng đó.
S1-3 chạy, giải phóng khóa ghi
Vì vậy, những thay đổi của S1 vẫn tồn tại. Một hàng đã biến mất. Một hàng đã được thêm vào.
S2-1 chạy, bây giờ nó có thể có được khóa. Nhưng báo cáo 0 hàng đã bị xóa. HUH???
Điều xảy ra trong nội bộ, là có một con trỏ từ một phiên bản của một hàng sang phiên bản tiếp theo của cùng một hàng nếu nó được cập nhật. Nếu hàng bị xóa, không có phiên bản tiếp theo. Khi một READ COMMITTED
giao dịch thức tỉnh từ một khối trên một xung đột ghi, nó sẽ theo chuỗi cập nhật đó đến hết; nếu hàng chưa bị xóa và nếu nó vẫn đáp ứng các tiêu chí lựa chọn của truy vấn thì nó sẽ được xử lý. Hàng này đã bị xóa, vì vậy truy vấn của S2 tiếp tục.
S2 có thể hoặc không thể đến hàng mới trong quá trình quét bảng. Nếu đúng như vậy, nó sẽ thấy rằng hàng mới đã được tạo sau khi DELETE
câu lệnh của S2 bắt đầu và do đó không phải là một phần của tập hợp các hàng hiển thị cho nó.
Nếu PostgreSQL khởi động lại toàn bộ câu lệnh XÓA của S2 ngay từ đầu bằng một ảnh chụp nhanh mới, thì nó sẽ hoạt động giống như SQL Server. Cộng đồng PostgreSQL đã không chọn làm điều đó vì lý do hiệu suất. Trong trường hợp đơn giản này, bạn sẽ không bao giờ nhận thấy sự khác biệt về hiệu suất, nhưng nếu bạn bị mười triệu hàng vào một DELETE
khi bạn bị chặn, bạn chắc chắn sẽ làm được. Có sự đánh đổi ở đây, nơi PostgreSQL đã chọn hiệu năng, vì phiên bản nhanh hơn vẫn tuân thủ các yêu cầu của tiêu chuẩn.
S2-2 chạy, báo cáo vi phạm ràng buộc khóa duy nhất
Tất nhiên, hàng đã tồn tại. Đây là phần ít ngạc nhiên nhất của bức tranh.
Mặc dù có một số hành vi đáng ngạc nhiên ở đây, mọi thứ đều phù hợp với tiêu chuẩn SQL và trong giới hạn của "đặc thù triển khai" theo tiêu chuẩn. Chắc chắn có thể đáng ngạc nhiên nếu bạn cho rằng một số hành vi của việc triển khai khác sẽ có mặt trong tất cả các lần triển khai, nhưng PostgreQuery cố gắng hết sức để tránh các lỗi tuần tự hóa ở READ COMMITTED
mức độ cô lập và cho phép một số hành vi khác với các sản phẩm khác để đạt được điều đó.
Bây giờ, cá nhân tôi không phải là một fan hâm mộ lớn của READ COMMITTED
mức độ cô lập giao dịch trong bất kỳ triển khai sản phẩm nào . Tất cả đều cho phép các điều kiện chủng tộc để tạo ra các hành vi đáng ngạc nhiên từ quan điểm giao dịch. Một khi ai đó đã quen với những hành vi kỳ lạ được cho phép bởi một sản phẩm, họ có xu hướng xem xét "bình thường" và sự đánh đổi được chọn bởi một sản phẩm kỳ quặc khác. Nhưng mỗi sản phẩm phải thực hiện một số loại đánh đổi cho bất kỳ chế độ nào không thực sự được thực hiện như SERIALIZABLE
. Trường hợp các nhà phát triển PostgreQuery đã chọn cách vẽ đường READ COMMITTED
này là để giảm thiểu việc chặn (đọc không chặn ghi và viết không chặn đọc) và để giảm thiểu khả năng thất bại nối tiếp.
Tiêu chuẩn yêu cầu SERIALIZABLE
các giao dịch là mặc định, nhưng hầu hết các sản phẩm không làm điều đó bởi vì nó gây ra hiệu suất vượt qua các mức cô lập giao dịch lỏng lẻo hơn. Một số sản phẩm thậm chí không cung cấp các giao dịch thực sự tuần tự hóa khi SERIALIZABLE
được chọn - đáng chú ý nhất là Oracle và các phiên bản của PostgreQuery trước 9.1. Nhưng sử dụng SERIALIZABLE
các giao dịch thực sự là cách duy nhất để tránh các tác động đáng ngạc nhiên từ các điều kiện cuộc đua và SERIALIZABLE
các giao dịch luôn phải chặn để tránh các điều kiện cuộc đua hoặc quay trở lại một số giao dịch để tránh tình trạng cuộc đua đang phát triển. Việc thực hiện SERIALIZABLE
giao dịch phổ biến nhất là Khóa hai pha nghiêm ngặt (S2PL) có cả lỗi chặn và tuần tự hóa (dưới dạng các khóa chết).
Tiết lộ đầy đủ: Tôi đã làm việc với Dan Cổng của MIT để thêm các giao dịch thực sự tuần tự hóa vào PostgreQuery phiên bản 9.1 bằng cách sử dụng một kỹ thuật mới có tên là Cách ly ảnh chụp nối tiếp.