Sự cách li giao dịch

Một phần của tài liệu Tài liệu quản trị postgresql (Trang 344 - 349)

Chương 13. Kiểm soát đồng thời

13.2. Sự cách li giao dịch

Tiêu chuẩn SQL định nghĩa 4 mức cách li giao dịch đối với 3 hiện tượng phải được ngăn chặn giữa các giao dịch đồng thời. Các hiện tượng không mong muốn đó là:

đọc bẩn

Một giao dịch đọc các dữ liệu được một giao dịch đồng thời không thực hiện được ghi lại đọc không lặp lại

Một giao dịch đọc lại các dữ liệu đã đọc rồi trước đó và thấy các dữ liệu đó từng bị một giao dịch khác (mà đã thực hiện kể từ lần đọc đầu tiên) sửa đổi.

đọc ma

Một giao dịch thực hiện lại một truy vấn trả về một tập hợp các hàng làm thỏa mãn một điều kiện tìm kiếm và thấy rằng tập hợp các hàng đó làm thỏa mãn điều kiện đã thay đổi vì giao dịch được thực hiện gần đây.

4 mức cách li giao dịch và các hành vi tương ứng được mô tả trong Bảng 13-1.

Bảng 13-1. Các mức cách li giao dịch SQL

Mức cách li Đọc bẩn Đọc không lặp lại Đọc ma

Read uncommitted - Đọc không thực hiện được

Possible - Có thể Possible - Có thể Possible - Có thể Read committed - Đọc thực hiện được Not possible - Không thể Possible - Có thể Possible - Có thể Repeatable read - Đọc lặp lại Not possible - Không thể Not possible - Không thể Possible - Có thể Serializable - Có khả năng tuần tự Not possible - Không thể Not possible - Không thể Not possible - Không thể

Trong PostgreSQL, bạn có thể yêu cầu bất kỳ mức nào trong 4 mức cách li giao dịch tiêu chuẩn.

Nhưng trong nội bộ, chỉ có 2 mức cách li phân biệt, tương ứng với các mức Đọc thực hiện được (Read Committed) và Có khả năng tuần tự (Serializable). Khi bạn chọn mức Đọc không thực hiện được (Read Uncommitted) thì bạn thực tế sẽ đọc thực hiện được (Read Committed), và khi bạn chọn Đọc lặp lại được thì thực tế bạn Có khả năng tuần tự (Serializable), nên mức cách li thực sự có thể là khắt khe hơn so với bạn chọn. Điều này được tiêu chuẩn SQL cho phép: 4 mức cách li chỉ định nghĩa hiện tượng nào phải không xảy ra, chúng không định nghĩa hiện tượng nào phải xảy ra.

Lý do là PostgreSQL chỉ đưa ra 2 mức cách li là điều này chỉ là cách nhạy cảm để ánh xạ các mức cách li tiêu chuẩn với kiến trúc kiểm soát đồng thời nhiều phiên bản. Hành vi của các mức cách li có sẵn được chi tiết hóa trong các phần sau.

Để thiết lập mức cách li của một giao dịch, hãy sử dụng lệnh SET TRANSACTION.

13.2.1. Mức cách li đọc thực hiện được

Đọc thực hiện được là mức cách li mặc định trong PostgreSQL. Khi một giao dịch sử dụng mức cách li này, thì một truy vấn SELECT (không có mệnh đề FOR UPDATE/SHARE) chỉ xem các dữ liệu được thực hiện trước khi truy vấn đó đã bắt đầu; nó không bao giờ xem hoặc các dữ liệu không được thực hiện, hoặc các thay đổi được thực hiện trong quá trình thực hiện truy vấn của các giao dịch đồng thời. Để có hiệu lực, một truy vấn SELECT xem một ảnh chụp cơ sở dữ liệu ngay khi truy vấn đó bắt đầu chạy. Tuy nhiên, SELECT sẽ xem các tác động của các cập nhật trước đó được thực hiện trong giao dịch của riêng nó, thậm chí dù chúng còn chưa được thực hiện xong. Hơn nữa lưu ý rằng 2 lệnh SELECT kế tiếp có thể xem các dữ liệu khác nhau, thậm chí dù chúng là trong một giao dịch duy nhất, nếu các giao dịch khác thực hiện các thay đổi trong quá trình thực thi lệnh SELECT

đầu tiên.

Các lệnh UPDATE, DELETE, SELECT FOR UPDATE, và SELECT FOR SHARE hành xử y hệt như lệnh SELECT

trong việc tìm kiếm các hàng đích: chúng sẽ chỉ tìm các hàng đích mà đã được thực hiện tại thời điểm bắt đầu của lệnh đó. Tuy nhiên, một hàng đích như vậy có thể đã được cập nhật rồi (hoặc được xóa hoặc bị khóa) vì một giao dịch đồng thời khác vào thời điểm nó được tìm thấy. Trong trường hợp này, việc cập nhật có thể sẽ chờ cho giao dịch cập nhật đầu tiên thực hiện xong hoặc quay lại (nếu nó vẫn còn diễn ra). Nếu việc cập nhật đầu tiên quay lại, thì các tác động của nó bị phủ định và việc cập nhật thứ 2 có thể tiến hành với việc cập nhật cho hàng ban đầu được tìm thấy. Nếu việc cập nhật đầu tiên thực hiện, thì việc cập nhật thứ 2 sẽ bỏ qua hàng đó nếu việc cập nhật đầu tiên đã xóa nó, nếu không thì nó sẽ cố áp dụng hành động của nó cho phiên bản được cập nhật của hàng đó.

Điều kiện tìm kiếm của lệnh (mệnh đề WHERE) được đánh giá lại để xem liệu phiên bản được cập nhật của hàng còn trùng khớp với điều kiện tìm kiếm hay không. Nếu có, thì việc cập nhật thứ 2 tiến hành hoạt động của nó bằng việc sử dụng phiên bản được cập nhật của hàng đó. Trong trường hợp các lệnh SELECT FOR UPDATE và SELECT FOR SHARE, điều này có nghĩa là phiên bản được cập nhật của hàng đó bị khóa và được trả về cho máy trạm.

Vì qui tắc ở trên, có khả năng cho một lệnh cập nhật xem một hình chụp không nhất quán: nó có thể xem các tác động của các lệnh cập nhật đồng thời lên các hàng y hệt mà nó đang cố gắng cập nhật, nhưng nó không xem các tác động của các lệnh đó lên các hàng khác trong cơ sở dữ liệu. Hành vi này làm cho chế độ Đọc thực hiện được không phù hợp với các lệnh có liên quan với các điều kiện tìm kiếm phức tạp; tuy nhiên, điều đó là đúng cho các trường hợp đơn giản hơn. Ví dụ, hãy cân nhắc việc cập nhật các bảng cân đối ngân hàng với các giao dịch như:

BEGIN;

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 12345;

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 7534;

COMMIT;

Nếu 2 giao dịch như vậy đồng thời cố gắng thay đổi bảng cân đối tài khoản 12345, thì chúng ta rõ ràng muốn giao dịch thứ 2 bắt đầu với phiên bản được cập nhật hàng của tài khoản đó. Vì từng lệnh đang có tác động chỉ cho một hàng được xác định trước đó, hãy để nó xem phiên bản được cập nhật của hàng không tạo ra bất kỳ sự không nhất quán đáng lo ngại nào.

Sử dụng phức tạp hơn có thể tạo ra các kết quả không mong muốn trong chế độ Đọc thực hiện được. Ví dụ, hãy cân nhắc việc hoạt động của một lệnh DELETE đối với các dữ liệu đang vừa được bổ sung thêm và vừa được loại bỏ khỏi các tiêu chí hạn chế của nó bằng một lệnh khác, như, giả thiết website là một bảng 2 hàng với website.hits bằng 9 và 10:

BEGIN;

UPDATE website SET hits = hits + 1;

-- chạy từ phiên khác: DELETE FROM website WHERE hits = 10;

COMMIT;

DELETE sẽ không có tác động thậm chí dù có một hàng website.hits = 10 trước và sau UPDATE. Điều này xảy ra vì giá trị 9 của hàng trước cập nhật, và khi UPDATE hoàn thành và DELETE có một sự khóa, thì giá trị hàng mới không còn là 10 mà là 11, nó không còn trùng khớp với các tiêu chí đó nữa.

Vì chế độ Đọc thực hiện được bắt đầu từng lệnh với một hình chụp mới mà bao gồm tất cả các giao dịch được thực hiện tùy thuộc vào sự tức thì đó, các lệnh tiếp sau trong cùng giao dịch sẽ thấy các

tác động của giao dịch đồng thời được thực hiện trong bất kỳ trường hợp nào. Điểm mấu chốt trong vấn đề ở trên là liệu có hay không một lệnh duy nhất thấy một kiểu nhìn nhất quán tuyệt đối cơ sở dữ liệu đó.

Sự cách li giao dịch một phần được chế độ Đọc thực hiện được cung cấp là phù hợp cho nhiều ứng dụng, và chế độ này là nhanh và đơn giản để sử dụng; tuy nhiên, nó là không đủ cho tất cả các trường hợp. Các ứng dụng mà thực hiện các truy vấn và các cập nhật phức tạp có thể đòi hỏi một kiểu nhìn nhất quán chặt chẽ của cơ sở dữ liệu hơn là chế độ Đọc thực hiện được đưa ra.

13.2.2. Mức cách li có khả năng tuần tự

Mức cách li có khả năng tuần tự đưa ra sự cách li giao dịch chặt chẽ nhất. Mức này giả lập thực thi giao dịch tuần tự, dường như các giao dịch từng được thực thi cái này sau cái kia, một cách tuần tự, thay vì đồng thời. Tuy nhiên, các ứng dụng sử dụng mức này phải được chuẩn bị để thử lại các giao dịch khi sự tuần tự bị hỏng.

Khi một giao dịch đang sử dụng mức tuần tự, một truy vấn SELECT chỉ thấy các dữ liệu được thực hiện trước khi giao dịch đó bắt đầu; nó không bao giờ thấy hoặc các dữ liệu không được thực hiện hoặc những thay đổi được thực hiện trong quá trình thực thi giao dịch đó của các giao dịch đồng thời. (Tuy nhiên, truy vấn đó thấy các hiệu ứng của các cập nhật trước đó được thực thi bên trong giao dịch của riêng nó, thậm chí dù chúng còn chưa được thực hiện xong). Điều này khác với Đọc thực hiện được theo đó một truy vấn trong một giao dịch tuần tự thấy một hình chụp như lúc bắt đầu giao dịch, không như lúc bắt đầu của truy vấn hiện hành trong giao dịch đó. Vì thế, các lệnh

SELECT tiếp sau trong một giao dịch duy nhất thấy các dữ liệu y hệt, nghĩa là, chúng không thấy các thay đổi được các giao dịch khác làm mà đã thực hiện xong sau khi giao dịch của riêng chúng đã bắt đầu. (Hành vi này có thể là lý tưởng cho việc báo cáo các ứng dụng).

Các lệnh UPDATE, DELETE, SELECT FOR UPDATE, và SELECT FOR SHARE hành xử y hệt như SELECT về việc tìm kiếm các hàng đích: chúng sẽ chỉ tìm các hàng đích mà đã được thực hiện xong như của thời điểm bắt đầu giao dịch. Tuy nhiên, một hàng đích như vậy có thể đã được cập nhật rồi (hoặc bị xóa hoặc khóa rồi) vì giao dịch đồng thời khác vào thời điểm nó được tìm thấy. Trong trường hợp này, giao dịch tuần tự sẽ chờ cho giao dịch cập nhật đầu tiên thực hiện xong hoặc quay lại (nếu nó vẫn còn diễn ra). Nếu cập nhật đầu tiên quay lại, thì các tác động của nó sẽ bị phủ định và giao dịch tuần tự có thể xử lý bằng việc cập nhật hàng được thấy ban đầu. Nhưng nếu cập nhật đầu tiên thực hiện (và thực tế đã cập nhật hoặc xóa hàng đó rồi, thì sẽ không khóa nó) rồi sau đó giao dịch tuần tự sẽ bị quay lại với thông điệp lỗi

ERROR: could not serialize access due to concurrent update (Lỗi: không thể truy cập tuần tự vì cập nhật đồng thời)

vì một giao dịch tuần tự không thể sửa hoặc khóa các hàng bị các giao dịch khác làm thay đổi sau khi giao dịch tuần tự đó đã bắt đầu.

Khi một ứng dụng nhận được thông điệp lỗi này, nó sẽ hủy bỏ giao dịch hiện hành và thử lại toàn bộ giao dịch đó từ đầu. Lần thứ 2 trôi qua, giao dịch đó sẽ thấy sự thay đổi được thực hiện rồi trước

đó như một phần của kiểu nhìn ban đầu của nó đối với cơ sở dữ liệu, nên sẽ không có xung đột logic trong việc sử dụng phiên bản mới của hàng đó như điểm bắt đầu đối với sự cập nhật của giao dịch mới.

Lưu ý rằng chỉ việc cập nhật các giao dịch có thể cần phải được thử lại; các giao dịch chỉ đọc sẽ không bao giờ có các xung đột tuần tự.

Chế độ tuần tự đưa ra một đảm bảo chặt chẽ rằng từng giao dịch thấy toàn bộ một kiểu nhìn nhất quán của cơ sở dữ liệu. Tuy nhiên, ứng dụng phải được chuẩn bị để thử lại các giao dịch khi các cập nhật đồng thời làm cho nó không có khả năng duy trì bền vững ảo tưởng đối với sự thực thi tuần tự.

Vì chi phí của việc hoãn thực hiện các giao dịch phức tạp có thể là đáng kể, nên chế độ tuần tự chỉ được khuyến cáo khi việc cập nhật các giao dịch có sự phức tạp logic đáng kể mà chúng có thể đưa ra các câu trả lời sai trong chế độ Đọc thực hiện được. Phổ biến nhất, chế độ tuần tự là cần thiết khi một giao dịch thực thi vài lệnh tiếp sau mà phải thấy các kiểu nhìn y hệt nhau của cơ sở dữ liệu.

13.2.2.1. Cách li tuần tự so với sự tuần tự đúng

Ý nghĩa trực quan (và định nghĩa toán học) của sự thực thi “tuần tự” là bất kỳ 2 giao dịch đồng thời được thực hiện thành công nào cũng sẽ dường như sẽ phải thực thi tuần tự một cách chặt chẽ, cái này sau cái kia - dù cái nào xảy ra trước đều có thể không đoán trước được trước đó. Điều này quan trọng để nhận thức được rằng việc cấm các hành vi không mong muốn được liệt kê trong Bảng 13-1 là không đủ để đảm bảo tính tuần tự đúng, và trong thực tế chế độ tuần tự được của PostgreSQL không đảm bảo sự thực thi có thể tuần tự được theo nghĩa này. Như một ví dụ, hãy cân nhắc một bảng mytab, ban đầu bao gồm:

class | value ---+--- 1 | 10 1 | 20 2 | 100 2 | 200

Giả thiết giao dịch tuần tự A tính:

SELECT SUM(value) FROM mytab WHERE class = 1;

và sau đó chèn kết quả (30) là value vào 1 hàng với class = 2. Đồng thời, giao dịch tuần tự B tính:

SELECT SUM(value) FROM mytab WHERE class = 2;

và có kết quả 300, mà nó chèn vào một hàng với class = 1. Sau đó cả 2 giao dịch thực hiện xong.

Không hành vi không mong muốn nào được liệt kê đã xảy ra, vâng chúng ta có một kết quả có thể đã không xảy ra trong cả trật tự một cách tuần tự. Nếu A đã thực thi trước B, thì B có thể đã tính tổng 330, chứ không phải là 300, và tương tự trật tự khác có thể đã dẫn tới một tổng số khác được A tính toán.

Để đảm bảo tính tuần tự toán học đúng, cần thiết đối với một hệ thống cơ sở dữ liệu ép tuân thủ việc khóa đuôi, nó có nghĩa là một giao dịch không thể chèn hoặc sửa đổi một hàng mà có thể đã trùng khớp điều kiện WHERE của một truy vấn trong giao dịch đồng thời khác. Ví dụ, một khi giao

dịch A đã thực thi truy vấn SELECT ... WHERE class = 1, thì một hệ thống khóa đuôi có thể cấm giao dịch B chèn bất kỳ hàng mới nào với class = 1 cho tới khi A đã thực hiện xong1. Một hệ thống khóa như vậy là phức tạp để triển khai và cực kỳ đắt trong thực thi, vì từng phiên phải nhận thức được các chi tiết của từng truy vấn được từng giao dịch đồng thời thực thi. Và chi phí lớn này hầu hết là phí phạm, vì trong thực tế hầu hết các ứng dụng không tiến hành sắp xếp những thứ mà có thể gây ra các vấn đề. (Chắc chắn ví dụ ở trên là được đặt ra và có lẽ không đại diện cho phần mềm thực tế).

Vì các lý do đó, PostgreSQL không triển khai khóa đuôi.

Trong các trường hợp nơi mà khả năng thực thi không tuần tự là một mối nguy có thực, thì các vấn đề có thể được ngăn chặn bằng việc sử dụng khóa độc quyền phù hợp. Thảo luận tiếp trong các phần tiếp sau.

Một phần của tài liệu Tài liệu quản trị postgresql (Trang 344 - 349)

Tải bản đầy đủ (PDF)

(372 trang)