Tổng quan về quản lý giao dịch

Trong chương này, chúng ta tìm hiểu xung quanh khái niệm giao dịch, là nền móng cho việc thực thi tương tranh và khôi phục lại cơ sở dữ liệu khi xảy ra sự cố trong một DBMS nào đó. Một giao dịch được định nghĩa là một thực thi của chương trình người dùng trong một DBMS, và nó khác với một thực thi của một chương trình bên ngoài DBMS (ví dụ, một chương trình C trên Unix). (Thực thi cùng một chương trình nhiều lần sẽ đưa ra nhiều giao dịch).

Vì những lý do thực thi, DBMS phải xen các thao tác của một vài giao dịch. Tuy nhiên, việc xen vào này được làm một cách cẩn thận để đảm bảo rằng kết quả của việc thực thi đồng thời các giao dịch vẫn tương đương với kết quả khi nó được thực thi một cách độc lập. DBMS quản lý các thực thi tương tranh như thế nào là một công việc quan trọng của quản lý giao dịch và là chủ đề của điều khiển tương tranh. Một vấn đề liên quan chặt chẽ đến điều khiển tương tranh là cách DBMS quản lý các giao dịch chưa thành công, hoặc các giao dịch bị hủy trước khi nó thành công. DBMS đảm bảo rằng những giao dịch khác sẽ không nhìn thấy những thay đổi do những giao dịch chưa thành công gây ra. Cách để thực hiện điều này là chủ đề của khôi phục sự cố. Trong chương này, chúng tôi cung cấp phần giới thiệu tổng quan về điều khiển tương tranh và khôi phục sự cố của DBMS. Hai chương sau sẽ đi sâu hơn về những vấn đề này.

Trong Phần 2, chúng tôi trình bày bốn tính chất cơ bản của các giao dịch và cách thức DBMS đảm bảo những tính chất này. Phần 2 trình bày một cách trừu tượng về thực hiện một vài giao dịch xen kẽ, được gọi là lịch trình. Phần 3 trình bày một vài vấn đề có thể phát sinh ra do việc thực thi xen lẫn gây ra. Chúng tôi giới thiệu về điều khiển tương tranh dựa-trên-khóa trong Phần 5, một cách tiếp cận được sử dụng rộng rãi. Phần 6 xem xét việc khóa và các tính chất của giao dịch trong ngữ cảnh của SQL. Cuối cùng, trong Phần 7, chúng tôi trình bày về cách một hệ cơ sở dữ liệu khôi phục sự cố và những bước phải làm trong suốt quá trình hỗ trợ khôi phục sự cố.

Tính chất ACID

Chúng tôi đã giới thiệu khái niệm giao dịch trong Phần 1.7. Tóm tắt lại, một giao dịch là một thực thi của một chương trình người dùng, DBMS coi nó là một chuỗi các thao tác đọc và ghi.

DBMS phải đảm bảo bốn tính chất của giao dịch để duy trì dữ liệu khi đối mặt với điều khiển tương tranh và những lỗi hệ thống:

1. Người dùng mong muốn việc thực thi mỗi giao dịch là nguyên tử (atomic): Tất cả các thao tác nằm trong giao dịch được thực hiện thành công hoặc thất bại hoàn toàn. Người dùng không phải lo lắng về ảnh hưởng của các giao dịch chưa được thành công (giả sử, có sự cố xảy ra khi giao dịch này đang trong quá trình thực hiện).

2. Mỗi giao dịch được thực thi không tranh chấp với các giao dịch khác, phải đảm bảo tính chất nhất quán (consistency) của cơ sở dữ liệu. DBMS thừa nhận rằng tính nhất quán được đảm bảo trên mỗi giao dịch. Việc đảm bảo tính chất này của giao dịch là trách nhiệm của người dùng.

3. Người dùng nên có thể hiểu được một giao dịch mà không cần xem xét những ảnh hưởng của các giao dịch tương tranh khác đang chạy, thậm chí DBMS có thể chèn vào các thao tác khác vì những lý do thực thi. Tính chất này đôi khi được nói tới như là tính chất cô lập (isolution). Các giao dịch được cô lập, hay còn gọi là được bảo vệ từ những ảnh hưởng của các giao dịch tương tranh khác.

4. Khi DBMS thông báo cho người dùng biết rằng giao dịch đã thành công hoàn toàn, những ảnh hưởng của nó nên được duy trì ngay cả khi hệ thống gặp sự cố trước khi tất cả những thay đổi này kịp lưu lại trên đĩa. Tính chất này được gọi là bền vững (durability).

Nhóm từ ACID đôi khi được sử dụng để nói đến bốn tính chất của giao dịch, là viết tắt của các từ: Atomicity, Consistency, Isolation và Durability. Bây giờ, chúng ta xem xét cách DBMS đảm bảo các tính chất này.

Nhất quán và Cô lập

Người dùng mong muốn có được sự đảm bảo về tính nhất quán của giao dịch. Tức là, người dùng yêu cầu thực hiện một giao dịch nào đó phải đảm bảo rằng khi hoàn thành giao dịch này cơ sở dữ liệu vẫn đảm bảo tính ‘nhất quán’. Ví dụ, người dùng có yêu cầu chuyển tiền giữa các tài khoản trong ngân hàng, việc chuyển tiền này phải đảm bảo rằng tổng giá trị các tài khoản sau khi chuyển không thay đổi. Để chuyển tiền từ một tài khoản này đến một tài khoản khác, giao dịch phải trừ tiền của một tài khoản- đưa cơ sở dữ liệu tới trạng thái không nhất quán tạm thời. Cơ sở dữ liệu trở nên nhất quán nếu số tiền này được cộng vào tài khoản thứ hai. Nếu có một lỗi nào đó xảy ra khiến tài khoản thứ hai nhận được ít hơn một đô la so với số tiền tài khoản thứ nhất đã bị trừ, DBMS không thể phát hiện được sự không nhất quán dữ liệu này.

Tính cô lập đảm bảo rằng mặc dù có một số hành động của các giao dịch có thể bị xen vào, thì các giao dịch vẫn được thực hiện một cách tuần tự. (Chúng tôi sẽ trình bày cách thức DBMS đảm bảo tính chất này trong Phần 4). Ví dụ, nếu hai giao dịch T1 và T2 được thực hiện tương tranh, kết quả cuối cùng được bảo đảm tương đương với việc thực hiện T1 sau T2 hoặc T2 sau T1. Nếu mỗi giao dịch dẫn đến các minh họa dữ liệu khác nhau, việc thực thi một vài giao dịch theo thứ tự (trên minh họa dữ liệu nhất quán ban đầu) sẽ thu được minh họa dữ liệu cuối cùng nhất quán.

Nhất quán cơ sở dữ liệu là tính chất mà khi thực hiện tất cả các giao dịch, các minh họa dữ liệu đều ở trạng thái nhất quán. Cơ sở dữ liệu nhất quán đạt được do các tính chất của giao dịch: nguyên tử, cô lập và nhất quán. Tiếp đến, chúng tôi trình bày về tính nguyên tử và bền vững trong DBMS.

Nguyên tử và Bền vững

Các giao dịch có thể không thành công được vì ba lý do. Đầu tiên, giao dịch có thể bị từ chối hoặc thực thi không thành công do trong quá trình thực hiện nó dẫn đến một dị thường dữ liệu nào đó. Nếu một giao dịch bị DBMS từ chối thực hiện vì lý do bên trong, nó sẽ khởi động lại và thực hiện một lần nữa. Thứ hai, hệ thống này có lẽ gặp sự cố (ví dụ, do mất nguồn điện) trong khi giao dịch đang thực hiện. Thứ ba, giao dịch có thể gặp phải một tình huống không mong muốn (ví dụ, đọc một giá trị dữ liệu không được mong muốn hoặc không thể truy cập tới một đĩa nào đó) và quyết định hủy bỏ nó.

Tất nhiên người dùng nghĩ rằng các giao dịch bị dừng giữa chừng này sẽ dẫn cơ sở dữ liệu đến trạng thái không nhất quán. Vì thế, DBMS phải tìm cách để loại bỏ những ảnh hưởng của các giao dịch này tới cơ sở dữ liệu. Tức là, nó phải đảm bảo tính nguyên tử của giao dịch: Tất cả các phép thực thi trong giao dịch sẽ thành công, hoặc thất bại hoàn toàn. DBMS đảm bảo tính nguyên tử của giao dịch bằng việc cho khôi phục lại những thao tác của các giao dịch chưa thành công. Người dùng có thể không cần để ý đến việc DBMS sửa các giao dịch chưa thành công như thế nào. Để có thể làm điều này, DBMS duy trì một bản ghi, gọi là lịch sử, lưu lại tất cả các thao tác ghi vào cơ sở dữ liệu. Thông tin lịch sử này được sử dụng để đảm bảo tính bền vững: Nếu hệ thống gặp sự cố trước khi những giao dịch đã thành công kịp lưu lên đĩa, thông tin lịch sử được sử dụng để ghi nhớ và hoàn thành phần việc chưa xong này khi hệ thống khởi động lại.

Các thành phần của DBMS đảm bảo tính chất nguyên tử và bền vững được gọi là quản lý phục hồi, chúng ta bàn thêm trong Phần 7.

Giao dịch và Lịch trình

Một giao dịch được DBMS xem như là một chuỗi, hay một danh sách các thao tác. Các thao tác này có thể bao gồm việc đọcghi các đối tượng cơ sở dữ liệu. Để đơn giản, chúng tôi giả sử rằng đối tượng O luôn được đọc vào một biến của chương trình cũng có tên là O. Chúng tôi có thể biểu diễn thao tác đọc đối tượng O của giao dịch T là RT(O), tương tự, chúng tôi có thể biểu diễn thao tác ghi là WT(O). Khi giao dịch T được xóa khỏi ngữ cảnh này, chúng ta hãy quên đi những ký hiệu này.

Đối với việc đọc và ghi, mỗi giao dịch phải chỉ rõ nó có thành công (giao dịch thành công hoàn toàn) hay hủy bỏ (tức là, giao dịch gặp sự cố và tất cả các thao tác đã thực hiện trong giao dịch phải khôi phục lại). Abort(T) biểu diễn thao tác T bị hủy bỏ, và Commit(T) biểu diễn T thành công.

Chúng ta có hai giả định quan trọng:

1. Các giao dịch ảnh hưởng lẫn nhau chỉ thông qua các thao tác đọc và ghi cơ sở dữ liệu: ví dụ, chúng không được phép trao đổi những thông báo.

2. Cơ sở dữ liệu là một tập cố định các đối tượng độc lập. Khi các đối tượng được thêm vào hoặc xóa khỏi cơ sở dữ liệu hoặc giữa các đối tượng tồn tại những mối quan hệ thì một vài vấn đề sẽ nảy sinh.

Nếu giả định đầu tiên bị vi phạm, DBMS sẽ không có cách nào để phát hiện hoặc chống lại sự không nhất quán của dữ liệu, và người viết ứng dụng không thể đảm bảo sự đúng đắn của chương trình. Chúng tôi giải thích giả định thứ hai trong Phần 6.2.

Lịch trình là một danh sách các thao tác (đọc, viết, từ chối thực hiện, hoặc thành công) của các giao dịch, và thứ tự hai thao tác trong giao dịch T xuất hiện trong lịch trình phải giống với thứ tự nó xuất hiện trong T. Lịch trình biểu diễn sự tuần tự thực hiện của các thao tác. Ví dụ, lịch trình trong Hình 2 chỉ ra thứ tự thực hiện của các thao tác trong hai giao dịch T1 và T2. Chúng tôi biểu diễn mỗi thao tác trong một dòng. Chúng tôi nhấn mạnh rằng lịch trình biểu diễn các thao tác của các giao dịch nhìn từ phía DBMS. Thêm vào những thao tác này, một giao dịch có thể thực hiện những thao tác khác, như đọc hoặc ghi từ các file hệ điều hành, đánh giá các biểu thức số học, vv…; tuy nhiên, chúng tôi giả sử rằng các thao tác này được thực hiện mà không ảnh hưởng đến những giao dịch khác.

Lịch trình của hai giao dịch

Ghi nhớ rằng lịch trình trong Hình 2 không chứa các thao tác hủy bỏ hoặc thành công của mỗi giao dịch. Lịch trình chứa cả những thao tác này được gọi là lịch trình đầy đủ. Một lịch trình đầy đủ phải chứa tất cả các thao tác của tất cả các giao dịch xuất hiện trong nó. Nếu các thao tác của các giao dịch khác nhau không được chèn vào, tức là, các giao dịch này được thực hiện từ bắt đầu tới kết thúc, tuần tự từng giao dịch- thì chúng tôi gọi lịch trình này là lịch trình tuần tự (serial schedule).

Các giao dịch thực thi tương tranh

Trong phần giới thiệu về khái niệm lịch trình ở trên, chúng tôi đã dùng một cách phù hợp để biểu diễn các các giao dịch thực hiện xen kẽ. Các thao tác này nhằm cải thiện khả năng thực thi của hệ thống, nhưng không phải tất cả mọi thao tác đều được phép. Trong phần này, chúng ta xem xét những thao tác, hay là những lịch trình nào DBMS cho phép.

Động cơ của thực thi tương tranh

Lịch trình trong Hình 2 biểu diễn sự thực hiện xen kẽ của hai giao dịch. Để đảm bảo tính cô lập của giao dịch trong khi cho phép hai giao dịch được thực hiện đồng thời là khó khăn nhưng cần thiết vì những lý do thực thi. Đầu tiên, trong khi một giao dịch đang đợi để đọc một trang vào từ đĩa, CPU có thể xử lý những giao dịch khác. Có được điều này bởi vì các thao tác I/O có thể được làm song song với các thao tác khác của CPU. Việc chồng thao tác I/O và thao tác trên CPU làm giảm thời gian nghỉ của CPU, đĩa và cải thiện khả năng của hệ thống (số lượng trung bình các giao dịch được hoàn thành trong một khoảng thời gian nào đó). Thứ hai, sự xen lẫn thực hiện của một giao dịch ngắn với một giao dịch dài thường cho phép giao dịch ngắn thực hiện nhanh hơn. Nếu thực hiện tuần tự, giao dịch ngắn có thể phải xếp hàng đằng sau một giao dịch dài, dẫn đến thời gian phản hồi chậm.

Sự tuần tự

Lịch trình tuần tự trên một tập S các giao dịch là một lịch trình mà kết quả thực hiện của nó tương đương với kết quả thực hiện các giao dịch này theo các lịch trình khác nhau.

Ví dụ, lịch trình trong Hình 2 là lịch trình tuần tự. Mặc dù các thao tác trong T1 và T2 xen kẽ nhau, nhưng kết quả của lịch trình này tương đương với việc thực hiện T1 (toàn bộ) và sau đó thực hiện T2. Như ta quan sát được, việc đọc và ghi đối tượng A của giao dịch T1 không ảnh hưởng đến việc đọc và ghi đối tượng A của giao dịch T2, và chúng ta có thể ‘đảo’ thứ tự T1, T2.

Lịch trình tuần tự

Việc thực hiện các giao dịch này theo các thứ tự khác nhau có thể đưa đến những kết quả khác nhau nhưng giả sử tất cả chúng đều được chấp nhận. Để nhìn thấy điều này, lưu ý rằng hai giao dịch trong Hình có thể được thực hiện như Hình: Lịch trình tuần tự khác. Lịch trình này cũng là lịch trình tuần tự, tương đương với lịch trình T2; T1. Nếu T1 và T2 được gửi tới DBMS đồng thời, cả hai lịch trình này có thể được lựa chọn.

Định nghĩa về lịch trình tuần tự phía trên không bao gồm trường hợp các lịch trình chứa các giao dịch bị hủy bỏ. Chúng ta mở rộng định nghĩa này để nó bao hàm cả các lịch trình bị hủy bỏ trong 4.

Lịch trình tuần tự khác

Cuối cùng, chúng ta ghi nhớ rằng một DBMS có thể thực hiện các giao dịch có lịch trình không tuần tự. Điều này có thể xảy ra vì hai lý do. Thứ nhất, DBMS này có lẽ sử dụng một cơ chế điều khiển tương tranh để đảm bảo các lịch trình không tuần tự được thực hiện như các lịch trình tuần tự (ví dụ xem trong Phần 6.2). Thứ hai, SQL cung cấp cho người lập trình ứng dụng khả năng chỉ dẫn cho DBMS để nó lựa chọn các lịch trình không tuần tự (xem Phần 6).

Những dị thường do thực thi xen kẽ

Hai thao tác trên cùng một đối tượng dữ liệu sẽ xảy ra xung đột nếu một trong số chúng là thao tác viết. Ba tình trạng dị thường có thể xảy ra khi các thao tác của hai giao dịch T1 và T2 xung đột với nhau: Trong xung đột viết-đọc (WR), T2 đọc một đối tượng dữ liệu trước khi T1 viết lên nó; tương tự, chúng ta định nghĩa xung đột đọc-viết (RW) và xung đột viết-viết (WW).

Đọc dữ liệu chưa được hoàn thành (xung đột WR)

Nguyên nhân đầu tiên dẫn đến dị thường là giao dịch T2 có thể đọc một đối tượng A đang được giao dịch T1 sửa, trong khi T1 chưa thành công. Việc đọc này được gọi là đọc bẩn (dirty read). Ví dụ đơn giản sau minh họa như thế nào một lịch trình có thể dẫn đến tình trạng cơ sở dữ liệu không nhất quán. Xem xét hai giao dịch T1 và T2, mỗi giao dịch này chạy độc lập sẽ dẫn đến tình trạng nhất quán của dữ liệu: T1 chuyển $100 từ A tới B, và T2 tăng cả A và B lên 6%. Giả sử rằng các thao tác này được thực hiện xen kẽ như sau (1) T1 thực hiện khấu trừ $100 của tài khoản A, sau đó (2) T2 đọc giá trị hiện tại của tài khoản A và B và tăng cả A và B lên 6%, và sau đó (3) tăng tài khoản B lên $100. Lịch trình này được minh họa trong Hình 4. Kết quả của lịch trình này khác với kết quả của lịch trình mà hai giao dịch thực hiện tuần tự. Vấn đề này xảy ra do T2 đọc đối tượng A trước khi T1 hoàn thành tất cả các thay đổi.

Đọc dữ liệu chưa hoàn thành

Minh họa chỉ ra rằng T1 có lẽ viết một số giá trị vào A làm thay đổi sự nhất quán của cơ sở dữ liệu. Việc này sẽ không gây hại gì nếu T1 và T2 được thực hiện tuần tự, bởi vì T2 sẽ không gặp phải sự không nhất quán (tạm thời). Tuy nhiên, việc thực thi xen kẽ này đã dẫn đến tình trạng không nhất quán tạm thời và trạng thái cuối cùng của cơ sở dữ liệu sẽ không nhất quán.

Ghi nhớ rằng mặc dù một giao dịch phải đưa cơ sở dữ liệu đến trạng thái nhất quán sau khi nó thành công, nhưng nó không yêu cầu phải nhất quán trong quá trình đang thực thi. Ví dụ: Để chuyển tiền từ một tài khoản này đến một tài khoản khác, giao dịch sẽ phải trừ tiền ở một tài khoản, dẫn đến tình trạng không nhất quán tạm thời của cơ sở dữ liệu, và sau đó cộng số tiền này vào tài khoản thứ hai, khôi phục lại trạng thái nhất quán của cơ sở dữ liệu.

Đọc không thể lặp lại (Xung đột RW)

Lý do thứ hai dẫn đến tình trạng không nhất quán là giao dịch T2 có thể thay đổi giá trị của đối tượng A trong khi đối tượng này đã được T1 đọc và T1 vẫn đang trong quá trình thực hiện.

Nếu T1 cố gắng đọc lại giá trị của A, nó sẽ có một kết quả khác. Tình trạng này có thể không xảy ra nếu hai giao dịch thực hiện tuần tự. Việc đọc này được gọi là đọc không thể lặp lại (unrepeatable read).

Để hiểu được lý do tại sao, chúng ta cùng xem xét ví dụ sau. Giả sử A là số bản sao của một quyển sách. Một giao dịch đặt hàng mua sách thực hiện việc đọc A, nó kiểm tra thấy rằng A đang lớn hơn 0, và giảm A đi. Giao dịch T1 đọc A và nhìn thấy giá trị là 1. Giao dịch T2 cũng đọc A và nhìn thấy giá trị là 1, giảm A xuống bằng 0 và hoàn thành giao dịch. Giao dịch T1 sau đó cố gắng giảm A và lỗi xảy ra (nếu ở đây có một ràng buộc toàn vẹn là không cho phép A có giá trị âm).

Tình trạng này có thể không bao giờ xảy ra nếu T1 và T2 thực hiện tuần tự; giao dịch thứ hai sẽ đọc A và nhìn thấy 0, vì thế nó sẽ không thực hiện hóa đơn này (và vì thế nó sẽ không cố gắng giảm giá trị của A).

Viết đè lên dữ liệu chưa hoàn thành (xung đột WW)

Lý do thứ ba dẫn đến dị thường là giao dịch T2 có thể viết đè giá trị lên đối tượng A- đối tượng đã được T1 sửa, trong khi T1 vẫn đang trong quá trình thực hiện. Ngay cả khi T2 không đọc giá trị của A do T1 đã viết lên, thì vẫn có vấn đề tiềm ẩn như minh họa sau.

Giả sử rằng Harry và Larry là hai nhân viên, lương của họ phải duy trì bằng nhau. Giao dịch T1 thiết đặt lương của họ là $2000 và giao dịch T2 thiết đặt là $1000. Nếu chúng ta thực hiện hai giao dịch này theo thứ tự T1 trước T2 thì cả hai người đều nhận lương là $1000; ngược lại nếu T2 trước T1 thì lương của họ là $2000. Ghi nhớ rằng không có giao dịch nào thực hiện thao tác đọc lương trước khi ghi đè lên nó- thao tác viết như vậy gọi là viết mù.

Bây giờ, đề cập đến việc thực thi xen kẽ các thao tác của T1 và T2: T2 thiết đặt lương của Harry là $1000, T1 thiết đặt lương của Larry là $2000, T2 thiết đặt lương của Larry là $1000 và giao dịch này được thành công, và cuối cùng T1 thiết đặt lương của Harry là $2000 và giao dịch được thành công. Kết quả này không tương đương với kết quả khi hai giao dịch thực hiện theo thứ tự, và lịch trình xen kẽ này vì thế không được gọi là lịch trình tuần tự. Nó vi phạm ràng buộc là lương của hai nhân viên này phải bằng nhau.

Lịch trình bao gồm các giao dịch bị hủy bỏ

Bây giờ chúng ta mở rộng định nghĩa về sự tuần tự tới cả những giao dịch bị hủy bỏ. Tất cả các thao tác của các giao dịch bị hủy bỏ phải được thực hiện lại, và vì thế chúng ta có thể tưởng tượng rằng giao dịch này không được thực hiện gì kể từ lúc nó bắt đầu. Sử dụng suy nghĩ này, chúng ta mở rộng định nghĩa về lịch trình tuần tự như sau: Một lịch trình tuần tự trên tập S các giao dịch là một lịch trình mà ảnh hưởng của nó trên bất kỳ minh họa cơ sở dữ liệu nhất quán nào được đảm bảo là tương đương với các lịch trình tuần tự khác của các giao dịch trong S.

Định nghĩa này dựa trên suy nghĩ rằng các thao tác của các giao dịch bị hủy bỏ sẽ được khôi phục lại hoàn toàn, điều này có thể không thực hiện được trong một vài trường hợp. Ví dụ, giả sử rằng (1) một chương trình chuyển tiền T1 khấu trừ $100 từ tài khoản A, sau đó (2) một chương trình tính lãi xuất T2 đọc giá trị hiện tại của tài khoản A và B và thêm vào đó 6%, sau đó nó thành công, và sau đó (3) T1 bị hủy bỏ. Lịch trình này được minh họa trong Hình 5.

Một lịch trình không thể khôi phục

Bây giờ, T2 đã đọc giá trị của A. (Nhớ lại rằng ảnh hưởng của các giao dịch bị hủy bỏ không hỗ trợ khả năng quan sát các giao dịch khác). Nếu T2 không thành công, chúng ta có thể phải đối mặt với tình trạng hủy bỏ chồng, T1 bị hủy bỏ và T2 cũng bị hủy bỏ theo, và cứ thế kéo theo những giao dịch khác. Nhưng T2 đã thành công, và vì thế chúng ta không thể khôi phục lại những thao tác trong nó. Chúng ta nói rằng lịch trình này là không thể khôi phục. Trong một lịch trình có thể khôi phục, các giao dịch thành công chỉ sau khi tất cả các giao dịch khác liên quan đã đọc xong những thay đổi của giao dịch này.

Có những vấn đề tiềm ẩn khác trong việc khôi phục lại các thao tác của một giao dịch. Giả sử rằng giao dịch T2 viết đè lên giá trị của đối tượng A – giá trị này đã được giao dịch T1 sửa, trong khi đó T1 vẫn đang trong quá trình thực thi, và T1 sau đó bị hủy bỏ. Tất cả các thay đổi của T1 trên các đối tượng cơ sở dữ liệu được khôi phục lại như trước khi T1 thực hiện. (Chúng ta tìm hiểu chi tiết về cách một giao dịch hủy bỏ được quản lý như thế nào trong Chương 18). Khi T2 bị hủy bỏ và những thay đổi của nó được khôi phục lại, những thay đổi của T2 cũng bị mất, ngay cả khi T2 quyết định thành công. Ví dụ, nếu A ban đầu có giá trị là 5, sau đó được T1 thay đổi thành 6, và được T2 thay đổi thành 7, nếu bây giờ T1 bị hủy bỏ, giá trị của A trở về bằng 5. Ngay cả khi T2 thành công, những tác động tới A cũng bị mất. Một công nghệ điều khiển tương tranh gọi là Strict 2PL được giới thiệu trong Phần 4 có thể tránh được vấn đề này .

Điều khiển tương tranh dựa trên khóa

DBMS phải được đảm bảo rằng chỉ có những lịch trình có khả năng khôi phục, và tuần tự là được phép và không có thao tác nào của các giao dịch thành công lại bị mất trong quá trình khôi phục các giao dịch bị hủy bỏ. DBMS thường sử dụng giao thức khóa để đạt được điều này. Khóa có thể được đặt trên các đối tượng cơ sở dữ liệu. Giao thức khóa là tập các quy tắc đằng sau mỗi giao dịch (và do DBMS thiết đặt) để đảm bảo rằng, mặc dù các thao tác của một số giao dịch khác có thể được chèn vào, nhưng kết quả cuối cùng sẽ tương đương với việc các giao dịch được thực hiện tuần tự. Các giao thức khóa khác nhau sử dụng các kiểu khóa khác nhau, như khóa chia sẻ hoặc khóa độc quyền, chúng ta sẽ bàn đến các loại khóa này trong phần giao thức Strict 2PL.

Khóa 2-pha nghiêm ngặt (Strict 2PL)

Giao thức khóa được sử dụng rộng rãi nhất, gọi là khóa hai pha nghiêm ngặt, hoặc Strict 2PL, có hai nguyên tắc. Thứ nhất là:

  1. Nếu một giao dịch T muốn đọc (sau đó là sửa) một đối tượng, đầu tiên nó yêu cầu một khóa chia sẻ (sau đó là khóa độc quyền) trên đối tượng này.

Tất nhiên, một giao dịch có khóa độc quyền cũng có thể đọc đối tượng này. DBMS đảm bảo rằng nếu một giao dịch nào đó nắm giữ khóa độc quyền trên một đối tượng, thì không có giao dịch nào khác được nắm giữ khóa chia sẻ và khóa độc quyền trên cùng đối tượng đó. Quy tắc thứ hai của Strict 2PL là:

  1. Tất cả khóa do một giao dịch nào đó nắm giữ sẽ được giải phóng khi giao dịch này hoàn thành.

Việc yêu cầu và giải phóng khóa của một giao dịch nào đó được DBMS thực hiện tự động; người dùng không phải lo lắng về vấn đề này. (Chúng ta bàn về cách thức người lập trình ứng dụng có thể lấy ra các thuộc tính của các giao dịch và điều khiển việc khóa trong Phần 6.3).

Về hiệu quả, giao thức khóa cho phép thực hiện các giao dịch xen kẽ một cách ‘an toàn’. Nếu hai giao dịch truy cập đến hai phần phân biệt của cơ sở dữ liệu, chúng sẽ có được các khóa chúng cần đồng thời và vui vẻ thực hiện các công việc của chúng. Ngược lại, nếu hai giao dịch cùng truy cập đến một đối tượng và một trong số chúng muốn sửa dữ liệu, những thao tác của chúng phải được thực hiện theo thứ tự- tất cả các thao tác của một trong số hai giao dịch này (giao dịch có khóa trên đối tượng này trước) được thành công trước khi (khóa này được giải phóng và) giao dịch thứ hai được xử lý.

Chúng ta biểu diễn thao tác của một giao dịch T yêu cầu khóa chia sẻ (tiếp đến là khóa độc quyền) trên đối tượng O là ST(O) (và XT(O)). Ví dụ, xem xét lịch trình trong Hình 4. Việc thực hiện xen kẽ này có thể dẫn đến kết quả không tương đương với kết quả thực hiện theo bất kỳ thứ tự nào của ba giao dịch. Ví dụ, T1 có thể thay đổi A từ 10 thành 20, sau đó T2 (đọc giá trị 20 của A) có thể thay đổi B từ 100 thành 200, và sau đó T1 sẽ đọc giá trị 200 của B. Nếu chạy tuần tự, T1 hoặc T2 sẽ thực hiện trước, và đọc giá trị 10 của A và 100 của B: Rõ ràng, việc thực hiện xen kẽ này không tương đương với thực thi tuần tự.

Lịch trình Strict 2PL

Nếu giao thức Strict 2PL được sử dụng, việc thực hiện xen kẽ như vậy sẽ không được phép. Hãy cùng chúng tôi nhìn xem vì sao. Giả sử rằng các giao dịch đều được xuất phát như trước, T1 sẽ có được khóa độc quyền trên A trước và sau đó đọc và ghi A (Hình 6). Sau đó, T2 sẽ yêu cầu khóa trên A. Tuy nhiên, yêu cầu này không được chấp nhận cho đến khi T1 giải phóng khóa độc quyền của nó trên A, và vì thế DBMS vẫn trì hoãn thực hiện T2. Bây giờ, T1 đã có khóa độc quyền trên B, nó thực hiện đọc và ghi B, sau đó kết thúc giao dịch, tại thời điểm này các khóa của T1 được giải phóng. Khóa của T2 bây giờ được phép và T2 tiến hành công việc của nó. Trong ví dụ này, các kết quả của giao thức khóa trong thực thi tuần tự hai giao dịch được chỉ ra trong Hình 7.

Lịch trình minh họa Strict 2PL với thực hiện tuần tự

Tuy nhiên, các thao tác của các giao dịch khác nhau có thể được thực thi xen kẽ. Ví dụ, xem xét việc thực hiện xen kẽ hai giao dịch chỉ ra trong Hình 8. Nó chỉ ra rằng thuận toán Strict 2PL cho phép chỉ những lịch trình tuần tự. Không có dị thường nào trình bày trong Phần 3.3 có thể xảy ra khi DBMS áp dụng Strict 2PL.

Lịch trình Strict 2PL với các thao tác xen kẽ

Khóa chết (DeadLocks)

Xem xét ví dụ sau. Giao dịch T1 đặt một khóa độc quyền trên đối tượng A, T2 đặt một khóa độc quyền trên B, T1 yêu cầu một khóa độc quyền trên B và nó được vào hàng đợi, và T2 yêu cầu một khóa độc quyền trên A và nó được vào hàng đợi. Bây giờ, T1 đang chờ T2 giải phóng khóa của nó và T2 chờ T1 giải phóng khóa của nó. Như vậy một vòng tròn của các giao dịch chờ đợi các khóa được giải phóng được gọi là khóa chết. Rõ ràng, hai giao dịch này sẽ không thể đi xa hơn được nữa. Tệ hơn, chúng nắm giữ các khóa mà có thể các giao dịch khác đang yêu cầu. DBMS phải tránh được tình trạng này hoặc phát hiện được nó; cách tiếp cận phổ biến là phát hiện và giải quyết nó.

Một cách đơn giản để giải quyết khóa chết là sử dụng cơ chế thời gian sống (timeout). Nếu một giao dịch phải chờ đợi quá lâu để có một khóa, chúng ta có thể cho rằng nó đang nằm trong vòng tròn khóa chết. Chúng ta bàn chi tiết hơn về khóa chết trong Phần 2.

Thực thi của khóa

Các lược đồ dựa trên khóa được thiết kế để giải quyết xung đột giữa các giao dịch và sử dụng hai cơ chế cơ bản: ngăn chặnhủy bỏ. Cả hai cơ chế này đều thực hiện: Các giao dịch đã bị ngăn chặn có thể nắm giữ các khóa làm cho các giao dịch khác phải đợi, và việc hủy bỏ và khởi động lại một giao dịch nào đó hiển nhiên là sẽ mất thời gian vì phải thực hiện lại những công việc mà giao dịch này đã làm được. Vấn đề khóa chết là một minh họa tuyệt vời về việc ngăn chặn, trong đó một tập các giao dịch sẽ vĩnh viễn bị ngăn lại trừ khi một trong số các giao dịch gặp hiện tượng khóa chết này được DBMS hủy bỏ.

Trong thực tế, ít hơn 1% giao dịch gặp rắc rối bởi khóa chết, và một số lượng tương đối bị hủy bỏ. Vì thế, tràn khóa là lý do chính dẫn đến việc ngăn chặn. Xem xét việc trì hoãn ngăn chặn làm ảnh hưởng đến throughtput (số lượng các giao dịch đưa vào) như thế nào. Đầu tiên, một vài giao dịch xung đột nhau, và throughput tăng lên tỷ lệ thuận với số lượng các giao dịch đang được thực hiện. Khi có nhiều giao dịch thực thi đồng thời trên cùng một số đối tượng cơ sở dữ liệu, rất có thể số lượng các giao dịch bị ngăn chặn tăng lên. Vì thế, việc trì hoãn dẫn đến tăng sự ngăn chặn tương ứng với số lượng các giao dịch đang thực hiện, và throughtput tăng chậm hơn nhiều so với số lượng các giao dịch đang thực hiện. Trên thực tế, có một điểm mà khi thêm vào các giao dịch khác sẽ làm giảm throughput. Chúng ta giả sử rằng hệ thống thrashing (bị nghẽn) ở điểm này, như minh họa trong Hình 9.

Lock Thrashing

Nếu một hệ thống cơ sở dữ liệu bắt đầu gặp hiện nghẽn, người quản trị cơ sở dữ liệu nên giảm số lượng các giao dịch được thực hiện đồng thời (active transactions). Theo kinh nghiệm, nghẽn quan sát được khi 30% giao dịch đang hoạt động bị ngăn lại, và DBA nên điều chỉnh lại tỷ lệ của các giao dịch bị ngăn lại nếu hệ thống gặp hiện tượng nghẽn.

Throughput có thể được tăng lên bằng ba cách:

  • Khóa trên các đối tượng có kích thước nhỏ nhất có thể được (giảm khả năng hai giao dịch cần cùng khóa).
  • Giảm thời gian nắm giữ khóa của giao dịch (để các giao dịch có thời gian bị ngăn chặn ngắn hơn).
  • Giảm các điểm nóng (hot spots). Điểm nóng là một đối tượng cơ sở dữ liệu được truy cập và cập nhật thường xuyên. Các điểm nóng có thể ảnh hưởng đáng kể đến khả năng thực thi.

Chúng tôi trình bày về cải thiện khả năng thực thi bằng cách tối thiểu hóa thời gian khóa nắm giữ và cách sử dụng các công nghệ để đối mặt với các điểm nóng trong Phần 20.10.

SQL hỗ trợ giao dịch

Phần trước chúng ta đã nghiên cứu về giao dịch và quản lý giao dịch. Bây giờ chúng ta xem xét việc SQL hỗ trợ người dùng quản lý giao dịch như thế nào.

Tạo và chấm dứt các giao dịch

Một giao dịch sẽ được khởi động một cách tự động khi người dùng thực thi một câu lệnh truy cập đến cơ sở dữ liệu hoặc các danh mục, như một truy vấn SQL, một lệnh cập nhật, hoặc một lệnh CREATE TABLE.

Khi giao dịch được khởi tạo, những câu lệnh của nó được thực hiện như một phần của giao dịch này cho đến khi giao dịch được kết thúc bằng lệnh COMMIT hoặc ROLLBACK.

Trong SQL: 1999, hai tính năng mới được cung cấp để hỗ trợ các ứng dụng có các giao dịch có thời gian chạy dài, hoặc phải chạy một vài giao dịch sau một giao dịch khác. Để hiểu được điều này, nhớ lại rằng tất cả các thao tác của một giao dịch phải được thực hiện theo thứ tự, không kể đến việc các thao tác của giao dịch khác được xen vào. Chúng ta có thể nghĩ rằng mỗi giao dịch là một tập các bước được thực hiện tuần tự.

Tính năng đầu tiên được gọi là savepoint, cho phép chúng ta xác định một điểm trong giao dịch mà trong trường hợp giao dịch bị hủy thì những thao tác trong giao dịch này sẽ được khôi phục lại tính từ điểm đằng sau từ khóa rollback.

SQL: 1999 Những giao dịch lồng nhau: Khái niệm về một giao dịch là một tập các thao tác được thực hiện một cách ‘nguyên tử’ đã được đưa vào trong SQL: 1999 với việc thêm tính năng savepoint. Điều này cho phép rollback lại một phần của giao dịch (chứ không phải toàn bộ). SQL hỗ trợ tính năng này trong các giao dịch lồng nhau. Ý tưởng là giao dịch có thể được thực hiện lồng trong những giao dịch khác, mỗi giao dịch con có thể được rollback.

Trong một giao dịch có thời gian chạy dài, chúng ta có thể muốn định nghĩa nhiều điểm savepoint:

SAVEPOINT (savepoint name)

Các lệnh rollback có thể chỉ ra điểm savepoint như sau:

ROLLBACK TO SAVEPOINT (savepoint name)

Nếu chúng ta định nghĩa ba điểm savepoint A, B và C theo thứ tự, và sau đó rollback tới điểm A thì tất cả các thao tác từ điểm A được khôi phục lại, bao gồm cả việc tạo ra các điểm savepoint là B và C. Thực tế, savepoint A tự bản thân nó sẽ mất khi chúng ta rollback tới nó, và nếu chúng ta muốn rollback lại điểm này một lần nữa, chúng ta phải xây dựng lại nó. Đứng trên quan điểm của khóa, các khóa có được sau điểm savapoint A có thể được giải phóng khi chúng ta rollback lại từ điểm A.

Bạn nên so sánh việc sử dụng các vị trí savepoints với cách tiếp cận thứ hai là xem tập các thao tác nằm giữa các vị trí savepoints là một giao dịch. Cơ chế savepoint có hai ưu điểm. Đầu tiên, chúng ta có thể rollback đến một vị trí savepoint bất kỳ. Trong khi cách tiếp cận thứ hai, chúng ta chỉ có thể rollback đến vị trí savepoint gần nhất. Thứ hai, sử dụng savepoint có thể tránh được overhead xảy ra khi khởi tạo nhiều giao dịch.

Ngay cả khi sử dụng cơ chế savepoint, các ứng dụng có thể yêu cầu chúng ta chạy một số giao dịch một cách tuần tự. Để giảm thiểu được overhead trong những tình huống này, SQL: 1999 giới thiệu một tính năng khác, gọi là chained transactions (giao dịch theo chuỗi). Chúng ta có thể cho một giao dịch nào đó thành công hoặc khôi phục lại và ngay lập tức khởi tạo một giao dịch khác. Thực hiện điều này bằng cách sử dụng từ khóa AND CHAIN trong các câu lệnh COMMIT và ROLLBACK.

Chúng ta nên khóa cái gì?

Cho đến bây giờ, chúng tôi đã trình bày về giao dịch và điều khiển tương tranh một cách trừu tượng trong đó cơ sở dữ liệu chứa một tập cố định các đối tượng, và mỗi giao dịch là một chuỗi các thao tác đọc và ghi lên các đối tượng riêng rẽ. Một câu hỏi quan trọng cần xem xét là trong ngôn ngữ SQL, DBMS coi cái gì là một đối tượng khi thiết đặt khóa ứng với một câu lệnh SQL nào đó (tức là, một phần của giao dịch).

Xem xét truy vấn sau:

SELECT S.rating, MIN (S.age)

FROM Sailors S

WHERE S.rating = 8

Giả sử rằng truy vấn này là một phần của giao dịch T1 và câu lệnh SQL sửa lại tuổi (age) của một thủy thủ nào đó, giả sử Joe – người có rating = 8, là một phần của giao dịch T2. ‘Đối tượng’ nào sẽ được DBMS khóa khi thực thi các giao dịch này? Bằng trực giác, chúng ta phải phát hiện được sự xung đột giữa các giao dịch này. DBMS có thể thiết đặt một khóa chia sẻ lên trên bảng Sailor cho T1 và lập một khóa độc quyền lên trên Sailors cho T2, điều này sẽ đảm bảo rằng hai giao dịch này được thực thi một cách có thứ tự. Tuy nhiên, cách tiếp cận này có hiệu quả thấp trong điều khiển tương tranh, và chúng ta có thể làm tốt hơn bằng việc khóa các đối tượng nhỏ hơn, chính là phần dữ liệu mà mỗi giao dịch thực sự truy cập đến. Vì thế, DBMS có thể thiết đặt một khóa chia sẻ chỉ trên tất cả các dòng có rating = 8 cho giao dịch T1 và thiết đặt khóa độc quyền chỉ trên dòng cần thay đổi cho giao dịch T2. Như vậy, những giao dịch chỉ-đọc khác (những giao dịch không bao gồm các dòng có rating=8) có thể được xử lý mà không cần đợi T1 và T2 thực thi xong.

Như trong minh họa của ví dụ này, DBMS có thể khóa các đối tượng ở các mức khác nhau: Chúng ta có thể khóa toàn bộ bảng hoặc thiết đặt các khóa mức-dòng. Khóa mức-dòng là tốt hơn nhưng lại phức tạp hơn. Ví dụ, một giao dịch cần kiểm tra một vài dòng thỏa mãn điều kiện tìm kiếm nào đó và thay đổi nó có thể sẽ thực hiện tốt nhất bằng cách thiết đặt khóa chia sẻ lên toàn bộ bảng và thiết đặt khóa độc quyền lên trên những dòng mà nó muốn thay đổi. Chúng tôi trình bày thêm về vấn đề này trong Phần 5.3.

Điểm thứ hai cần lưu ý là các câu lệnh SQL truy cập đến một tập các bản ghi thỏa mãn một số điều kiện nào đó. Trong ví dụ trước, giao dịch T1 truy cập tất cả các dòng có rating=8. Chúng tôi đã đề nghị rằng có thể giải quyết vấn đề này bằng cách đặt khóa chia sẻ lên trên tất cả các dòng của Sailors có rating=8. Không may thay, điều này không đơn giản như vậy. Để nhìn thấy vì sao, xem xét câu lệnh SQL để thêm một sailor mới có rating=8 và chạy câu lệnh này trong giao dịch T3. (Quan sát thấy rằng ví dụ này vi phạm giả thiết của chúng ta là cố định số lượng đối tượng trong cơ sở dữ liệu, nhưng trên thực tế chúng ta phải đối mặt với tình trạng này).

Giả sử rằng DBMS thiết đặt các khóa chia sẻ lên trên tất cả các dòng Sailors đang tồn tại có rating=8 cho T1. Điều này không ngăn được giao dịch T3 tạo một dòng mới có rating=8 và đặt một khóa độc quyền lên trên dòng này. Nếu dòng mới này có giá trị age nhỏ hơn các dòng đang tồn tại, kết quả trả về của T1 phụ thuộc vào thời điểm nó được thực hiện liên quan đến T2. Tuy nhiên, lược đồ khóa của chúng ta đã được đưa ra mà không đề cập đến thứ tự của hai giao dịch này.

Hiện tượng này được gọi là phantom (dữ liệu ma): Một giao dịch nào đó truy cập đến một tập các đối tượng (trong SQL gọi là một tập các bộ giá trị) hai lần và nhìn thấy những kết quả khác nhau, ngay cả khi nó không sửa bất kỳ bộ giá trị nào. Để tránh vấn để phantom, DBMS phải khóa tất cả các dòng có khả năng có rating=8 cho T1. Một cách để làm điều này là khóa toàn bộ bảng, chấp nhận khả năng xử lý tương tranh thấp. Chúng ta cũng có thể sử dụng những ưu điểm của chỉ số để làm điều này tốt hơn, phần này sẽ được bàn đến trong Phần 5.1, nhưng nói chung việc ngăn chặn phantoms có thể có ảnh hưởng đáng kể trong điều khiển tương tranh.

Sẽ là tốt nếu ứng dụng này thỏa hiệp được để T1 chấp nhận khả năng không chính xác của dữ liệu mà từ đó có thể dẫn đến phantoms. Nếu được như vậy, các tiếp cận là thiết đặt các khóa chia sẻ lên trên các bộ giá trị đang tồn tại cho T1 là phù hợp vì nó sẽ cải thiện được khả năng thực thi của hệ thống. SQL cho phép người lập trình thực hiện lựa chọn này- và những lựa chọn tương tự khác, chúng ta sẽ tìm hiểu trong phần tiếp theo.

Các tính chất của giao dịch trong SQL

Để cung cấp cho người lập trình khả năng điều khiển khóa trên các giao dịch của họ, SQL cho phép họ xác định ba tính chất của một giao dịch: phương thức truy cập, kích thước chuẩn đoán, và mức cô lập. Kích thước chuẩn đoán chỉ ra số lượng các lỗi có thể được ghi lại; chúng ta sẽ bàn thêm về tính chất này.

Nếu phương thức truy cập là READ ONLY, giao dịch này sẽ không được phép sửa cơ sở dữ liệu. Vì thế, các câu lệnh INSERT, DELETE, UPDATE, và CREATE sẽ không được phép thực thi. Nếu chúng ta phải thực hiện một trong số các câu lệnh này, phương thức truy cập sẽ phải được thiết đặt là READ WRITE. Với những giao dịch có phương thức truy cập là READ ONLY, chỉ cần thực hiện các khóa chia sẻ, vì thế nó sẽ tăng khả năng thực hiện tương tranh.

Dựa vào mức độ ‘dung thứ’ với những dữ liệu không chính xác, mức cô lập được phân thành bốn loại: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, và SERIALIZABLE. Ảnh hưởng của những mức cô lập này được tổng kết trong Hình 10. Trong ngữ cảnh này, dirty read unrepeatable read được định nghĩa như thông thường.

Các mức cô lập của SQL-92

Mức cô lập cao nhất của giao dịch T là SERIALIZABLE. Mức cô lập này đảm bảo rằng T chỉ đọc những thay đổi được làm bằng các giao dịch đã thành công, không có giá trị nào được đọc hoặc viết bởi T lại được một giao dịch nào khác thay đổi cho đến khi T thành công, và nếu T đọc một tập các giá trị thỏa mãn một vài điều kiện tìm kiếm, tập này không được thay đổi bởi các giao dịch khác cho đến khi T thành công (tức là, T tránh được hiện tượng phantom).

Khi thực thi dựa trên khóa, một giao dịch SERIALIZABLE có được các khóa trước khi đọc hoặc ghi các đối tượng, bao gồm các khóa trên tập các đối tượng có yêu cầu thay đổi và nắm các khóa này cho đến khi kết thúc, theo Strict 2PL.

REPEATABLE READ đảm bảo rằng T chỉ đọc những thay đổi được làm bằng các giao dịch đã thành công và không có giá trị nào được đọc hoặc ghi bởi T lại được một giao dịch khác thay đổi cho đến khi T thành công. Tuy nhiên, T có thể vẫn gặp hiện tượng phantom; ví dụ, trong khi T kiểm tra tất cả các bản ghi của Sailors có rating=1, giao dịch khác có thể thêm một bản ghi mới cho Sailors mà giao dịch T đã bỏ lỡ.

Một giao dịch REPEATABLE READ thiết đặt tập các khóa như là giao dịch SERIALIZABLE, nhưng nó không thực hiện việc khóa chỉ số; tức là, nó chỉ khóa những đối tượng độc lập, không phải là tập các đối tượng.

READ COMMITTED đảm bảo rằng T chỉ đọc những thay đổi được làm bởi các giao dịch đã thành công, và không có giá trị nào được viết bởi T lại được một giao dịch khác thay đổi cho đến khi T thành công. Tuy nhiên, giá trị được đọc bởi T có thể được sửa bởi các giao dịch khác trong khi T vấn đang trong quá trình thực thi, và T có khả năng gặp phải hiện tượng phantom.

Giao dịch READ COMMITTED có được các khóa độc quyền trước khi thực hiện việc viết lên đối tượng và nắm giữ những khóa này cho đến khi kết thúc. Nó cũng có được các khóa chia sẻ trước khi đọc các đối tượng, nhưng những khóa này được giải phóng ngay lập tức.

Giao dịch READ UNCOMMITTED T có thể đọc những đối tượng đang được giao dịch khác thay đổi; rõ ràng, đối tượng này có thể được thay đổi trong khi T đang trong quá trình thực thi, và T rất có thể gặp phải vấn đề phantom.

Giao dịch READ UNCOMMITTED không có được khóa chia sẻ trước khi đọc các đối tượng; giao dịch này dễ gặp phải vấn đề phantom nhất; đến mức mà SQL ngăn cấm giao dịch này tự bản thân nó làm bất kỳ thay đổi nào – giao dịch READ UNCOMMITTED yêu cầu phải có phương thức truy cập READ ONLY. Vì giao dịch này không có được khóa cho việc đọc các đối tượng và nó không được phép viết lên đối tượng (và vì thế khóa độc quyền không bao giờ được yêu cầu), nên nó không bao giờ có bất kỳ yêu cầu khóa nào.

Mức cô lập SERIALIZABLE an toàn nhất và được nhiều giao dịch yêu cầu nhất. Tuy nhiên, một số giao dịch có thể chạy với mức cô lập thấp hơn, và số lượng các khóa được yêu cầu nhỏ hơn sẽ góp phần để cải thiện khả năng thực thi của hệ thống. Ví du, một truy vấn thống kê yêu cầu tìm tuổi trung bình của các thủy thủ có thể chạy ở mức cô lập là READ COMMITTED hoặc thậm chí ở mức READ UNCOMMITTE, bởi vì một vài giá trị thiếu hoặc không chính xác sẽ không ảnh hưởng đáng kể đến kết quả cuối cùng do số lượng thủy thủ lớn.

Mức cô lập và phương thức truy cập có thể được thiết đặt sử dụng lện SET TRANSACTION. Ví dụ, lệnh sau xác định giao dịch hiện tại là SERIALIZABLE và READ ONLY:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY

Khi một giao dịch được khởi tạo, giá trị mặc định là SERIALIZABLE và READ WRITE.

Giới thiệu về khôi phục sự cố

Quản lý sự cố của một DBMS có nhiệm vụ đảm bảo giao dịch là nguyên tử và bền vững. Nó đảm bảo tính nguyên tử bằng việc khôi phục lại những thao tác của các giao dịch chưa thành công, và bền vững bằng cách đảm bảo rằng tất cả thao tác của các giao dịch thành công vẫn được thực hiện đầy đủ nếu hệ thống gặp sự cố.

Khi DBMS khởi động lại sau sự cố, hệ thống quản lý khôi phục phải mang cơ sở dữ liệu trở về trạng thái nhất quán. Hệ thống quản lý sự cố có nhiệm vụ khôi phục lại những thao tác của những giao dịch bị hủy bỏ. Để xem hệ thống khôi phục sự cố làm gì, chúng ta cần phải hiểu những gì xảy ra trong suốt quá trình thực hiện bình thường.

Quản lý giao dịch của một DBMS điều khiển việc thực thi của các giao dịch. Trước khi đọc và ghi các đối tượng, các khóa phải được yêu cầu (và giải phóng ở thời điểm sau đó) tùy thuộc vào giao thức khóa được lựa chọn. Để đơn giản, chúng ta có giả sử sau:

Viết nguyên tử: Việc viết một trang nào đó lên đĩa là một thao tác nguyên tử.

Điều này ngụ ý rằng hệ thống sẽ không gặp sự cố trong quá trình viết. Trên thực tế, việc viết lên đĩa không có tính chất này, và nhiều bước phải được thực hiện trong quá trình khởi tạo sau sự cố (Phần 18.6) để đảm bảo rằng việc ghi gần đây nhất lên một trang nào đó phải được hoàn thành thành công, và đối phó với những hậu quả nếu việc ghi này không thành công.

Các khung dữ liệu bị lấy cắp và các trang bị cưỡng chế

Đối với việc viết lên các đối tượng, có hai câu hỏi sau nảy sinh:

1. Những thay đổi trên đối tượng O trong buffer pool được giao dịch T thực hiện có thể được viết lên trên đĩa trước khi T thành công không? Việc viết này được thực hiện khi giao dịch khác muốn đưa vào một trang và hệ thống quản lý vùng đệm lựa chọn frame chứa đối tượng O để thay thế. Nếu việc viết như vậy được phép, chúng ta nói rằng cách tiếp cận steal được sử dụng. (Nói một cách thân mật, giao dịch thứ hai ‘lấy cắp (steal)’ một frame từ T).

2. Khi một giao dịch thành công, chúng ta phải đảm bảo rằng tất cả các thay đổi nó đã làm với các đối tượng trong buffer pool được áp đặt tới đĩa ngay lập tức? Nếu vậy, chúng ta nói rằng cách tiếp cận force được sử dụng.

Đứng trên quan điểm thực thi của hệ thống quản lý khôi phục, đơn giản nhất là sử dụng một hệ thống quản lý vùng đệm với cách tiếp cận là force, no-steal. Nếu cách tiếp cận no-steal được sử dụng, chúng ta không phải khôi phục lại những thay đổi của giao dịch bị hủy bỏ (vì những thay đổi này chưa được ghi lên đĩa), và nếu cách tiếp cận force được sử dụng, chúng ta khôi phục lại những thay đổi của giao dịch đã thành công nếu sự cố đến sau (bởi vì tất cả các thay đổi được đảm bảo đã được ghi lên đĩa ở thời điểm giao dịch thành công).

Tuy nhiên, những chính sách này có những nhược điểm quan trọng. Cách tiếp nhận no-steal cho rằng tất cả các trang được sửa bằng các giao dịch đang thực hiện đều có thể nằm trong buffer pool, giả sử này không thực tế. Còn cách tiếp cận force phải trả giá cho I/O quá đắt. Nếu một trang được cập nhật liên tiếp bởi 20 giao dịch, nó sẽ được viết lên đĩa 20 lần. Mặt khác, với cách tiếp cận no-force, việc sửa và viết lên đĩa một trang chỉ bằng một lần dù cho có 20 thao tác cập nhật nếu trang này nằm trọn vẹn trong buffer pool.

Vì những lý do này, hầu hết các hệ thống đều sử dụng cách tiếp cận steal, no-force. Vì thế, nếu frame bẩn và được lựa chọn để thay thế, những trang nằm trong frame này được viết lên đĩa ngay cả khi giao dịch đang sửa vẫn trong quá trình thực hiện (steal); thêm nữa, các trang trong buffer pool được sửa bằng một giao dịch nào đó không bắt buộc phải ghi lên đĩa khi giao dịch thành công (no-force).

Các bước khôi phục của một thực thi thông thường

Quản lý khôi phục của một DBMS duy trì một số thông tin trong suốt quá trình thực thi thông thường để nó có thể đảm bảo thực hiện được các công việc cần thiết khi sự cố xảy ra. Cụ thể, lịch sử của tất cả các thay đổi tới cơ sở dữ liệu được ghi lại một cách ổn định. Việc ghi lại này được thực hiện bằng cách duy trì nhiều bản sao của thông tin (có thể trong các vùng khác nhau) trên các thiết bị lưu trữ như đĩa và băng từ.

Như những trình bày trong Phần 7, một điều quan trọng là đảm bảo rằng toàn bộ lịch sử của những thay đổi phải được lưu trữ trên các thiết bị an toàn trước khi những thay đổi đó thực sự được thực hiện. (Nhớ lại rằng cách thực thi này đã được biết đến là Write-Ahead Log, hay còn gọi là WAL).

Tinh chỉnh hệ thống khôi phục phụ: Thực thi của DBMS có thể có hiệu quả cao nhờ vào các hệ thống khôi phục phụ. Người quản trị hệ thống có thể mất một vài bước để tinh chỉnh những hệ thống này, như chính xác hóa kích thước của file lịch sử và quản lý nó trên đĩa như thế nào, điều khiển tốc độ các trang đệm được ghi lên đĩa, lựa chọn một chu kỳ checkpointing tốt và vv…

Thông tin lịch sử này có thể giúp hệ thống quản lý khôi phục phục hồi lại các thao tác của các giao dịch chưa hoàn thành và các giao dịch bị hủy bỏ. Ví dụ, một giao dịch nào đó đã thành công trước khi sự cố xảy ra đã cập nhật xong tới bản sao của đối tượng trong buffer pool, và thay đổi này chưa kịp viết lên đĩa vì nó sử dụng cách tiếp cận no-force. Những thay đổi này phải được xác định bằng cách sử dụng thông tin lịch sử này và sau đó thực hiện việc viết lên đĩa. Thêm nữa, những thay đổi của các giao dịch không thành công trước khi sự cố xảy ra có lẽ đã được viết lên đĩa do cách tiếp cận steal. Những thay đổi này phải được xác định nhờ vào thông tin lịch sử này và sau đó khôi phục lại.

Khối lượng công việc trong quá trình khôi phục tương ứng với những thay đổi đã được làm bởi các giao dịch thành công mà chưa kịp ghi lên đĩa trước khi xảy ra sự cố. Để giảm thời gian dành cho khôi phục sự cố, DBMS định kỳ bắt buộc các trang đệm phải ghi lên đĩa trong quá trình thực thi sử dụng một tiến trình nền (trong khi vẫn đảm bảo rằng toàn bộ lịch sử ghi lại những thay đổi của những trang này phải được ghi lên đĩa trước tiên). Một tiến trình được gọi là checkpointing, lưu lại những thông tin về các giao dịch đang hoạt động và các trang buffer pool bẩn, cũng như giúp giảm thời gian khôi phục một sự cố. Checkpoints được trình bày trong Phần 18.5.

Tổng quan về ARIES

ARIES là một thuật toán khôi phục được thiết kế để phục vụ cách tiếp cận steal, no-force. Khi hệ thống khôi phục được yêu cầu sau khi một sự cố xảy ra, nó thực hiện ba bước. Trong bước Phân tích, nó xác định các trang bẩn trong buffer pool (tức là, những thay đổi đã không được ghi lên đĩa) và các giao dịch đang hoạt động ở thời điểm xảy ra sự cố. Trong bước Khôi phục lại, nó lặp lại tất cả các thao tác, khởi động lại từ một điểm thích hợp trong file lịch sử, và khôi phục tới trạng thái cơ sở dữ liệu ở thời điểm xảy ra sự cố. Cuối cùng, trong bước Khôi phục lại, nó không thực hiện lại những thao tác của các giao dịch chưa thành công, để cơ sở dữ liệu phản ánh những thao tác của các giao dịch đã thành công. Thuật toán ARIES được trình bày sâu hơn trong Chương 18.

Tính nguyên tử: Việc thực thi Rollback

Là quan trọng để nhận ra rằng hệ thống khôi phục phụ cũng có nhiệm vụ thực thi lệnh ROLLBACK một giao dịch đơn. Thực tế, những công việc phức tạp trong quá trình khôi phục lại một giao dịch đơn được xác định trong bước Khôi phục lại. Tất cả các bản ghi lịch sử ứng với một giao dịch nào đó được tổ chức trong một danh sách liên kết và có thể được truy cập một cách hiệu quả theo thứ tự nghịch đảo để thuận lợi cho việc rollback giao dịch.

Câu hỏi tổng kết

Câu trả lời của những câu hỏi sau có thể được tìm thấy trong phần bên cạnh.

  • Tính chất ACID là gì? Định nghĩa tính nguyên tử, tính nhất quán, tính cô lập, tính bền vững và minh họa chúng thông qua các ví dụ. (Phần 1)
  • Định nghĩa các khái niệm giao dịch, lịch trình, lịch trình đầy đủ lịch trình tuần tự. (Phần 2)
  • Vì sao DBMS thực hiện các giao dịch đồng thời một cách xen kẽ? (Phần 3)
  • Khi nào hai thao tác trên cùng một đối tượng xung đột nhau? Định nghĩa những dị thường có thể xảy ra do xung đột (đọc bẩn, đọc không thể lặp lại). (Phần 3)
  • Lịch trình tuần tự là gì? Lịch trình có thể khôi phục là gì? Lịch trình nào có thể tránh được hủy bỏ chồng? Lịch trình nghiêm ngặt là gì? (Phần 3)
  • Giao thức khóa là gì? Trình bày về giao thức khóa hai-pha nghiêm ngặt (Strict 2PL). Bạn có thể nói gì về các lịch trình được giao thức này cho phép? (Phần 4)
  • Trình bày về việc ngăn chặnhủy bỏ và giải thích nó quan trọng như thế nào trong thực tế. (Phần 5)
  • Thrashing là gì? DBA nên làm gì khi hệ thống gặp hiện tượng này? (Phần 5)
  • Throughtput có thể được tăng lên như thế nào? (Phần 5)
  • Các giao dịch được tạo và hủy bỏ như thế nào trong SQL? Savepoints là gì? Các giao dịch chuỗi là gì? Giải thích savepoints và giao dịch chuỗi hữu dụng vì sao? (Phần 6)
  • Vấn đề phantom là gì? Nó ảnh hưởng gì tới thực thi? (Phần 6.2)
  • Các tính chất nào của giao dịch có thể được người lập trình điều khiển trong SQL? Trình bày về sự khác nhau giữa các phương thức truy cập và các mức cô lập? Những vấn đề nào nên được đề cập trong việc lựa chọn phương thức truy cập và mức cô lập cho một giao dịch nào đó? (Phần 6.3)
  • Trình bày như thế nào các mức cô lập khác nhau được thực thi khi các khóa được thiết đặt. Bạn có thể nói gì về chi phí khóa tương ứng (Phần 6.3)
  • Tính năng nào hệ thống quản lý khôi phục của DBMS cung cấp? Hệ thống quản lý giao dịch làm gì? (Phần 7)
  • Trình bày chính sách stealforce trong ngữ cảnh của quản lý bộ đệm. Chính sách nào được sử dụng trong thực tế và nó ảnh hưởng đến khôi phục như thế nào? (Phần 7.1)
  • Các bước khôi phục trong quá trình thực thi thông thường? DBA có thể điều khiển gì để giảm thời gian khôi phục sau một sự cố? (Phần 7.2)
  • Thông tin lịch sử được sử dụng trong rollback giao dịch và khôi phục sự cố như thế nào? (Phần 7.2, 7.3, và 7.4)

BÀI TẬP

Trả lời tóm tắt những câu hỏi sau:

  1. Giao dịch là gì? Nó khác với chương trình bình thường khác (trong một ngôn ngữ lập trình như C) như thế nào?
  2. Định nghĩa những khái niệm sau: tính nguyên tử, tính nhất quán, tính cô lập, và tính bền vững, lịch trình, viết mù, đọc bẩn, đọc không thể lặp lại, lịch trình có khả năng tuần tự, lịch trình có khả năng phục hồi, lịch trình tránh hủy bỏ chồng.
  3. Trình bày Strict 2PL.
  4. Vấn đề phantom là gì? Nó có thể xảy ra trong một cơ sở dữ liệu mà ở đó tập các đối tượng được cố định, chỉ có giá trị trong đó có thể thay đổi?

Câu trả lời cho mỗi câu hỏi như sau:

1. Một Giao dịch là một thực thi của một chương trình người dùng, và được DBMS xem như một chuỗi các thao tác. Các thao tác này có thể được thực hiện bằng một giao dịch nào đó bao gồm: các phép đọc và viết lên các đối tượng cơ sở dữ liệu, trong khi đó các thao tác trong một chương trình thông thường có thể bao gồm dữ liệu đầu vào của người dùng, truy cập các thiết bị mạng, xây dựng giao diện người dùng, vv...

2. Từng khái niệm được mô tả như sau:

a. Nguyên tử (atomic): Tất cả các thao tác nằm trong giao dịch được thực hiện thành công hoặc thất bại hoàn toàn. Người dùng không phải lo lắng về ảnh hưởng của các giao dịch chưa được thành công (giả sử, có sự cố xảy ra khi giao dịch này đang trong quá trình thực hiện).

b. Nhất quán: Mỗi giao dịch được thực thi không tranh chấp với các giao dịch khác, phải đảm bảo tính chất nhất quán của cơ sở dữ liệu. DBMS thừa nhận rằng tính nhất quán được đảm bảo trên mỗi giao dịch. Việc đảm bảo tính chất này của giao dịch là trách nhiệm của người dùng.

c. Cô lập: Người dùng nên có thể hiểu được một giao dịch mà không cần xem xét những ảnh hưởng của các giao dịch tương tranh khác đang chạy, thậm chí DBMS có thể chèn vào các thao tác khác vì những lý do thực thi. Tính chất này đôi khi được nói tới như là tính chấtcô lập.Các giao dịch được cô lập, hay còn gọi là được bảo vệ từ những ảnh hưởng của các giao dịch tương tranh khác.

d. Bền vững: Khi DBMS thông báo cho người dùng biết rằng giao dịch đã thành công hoàn toàn, những ảnh hưởng của nó nên được duy trì ngay cả khi hệ thống gặp sự cố trước khi tất cả những thay đổi này kịp lưu lại trên đĩa.

(e) Lịch trình là một chuỗi các giao dịch (có thể xếp chồng).

(f) Viết mù là việc một giao dịch nào đó viết lên một đối tượng mà thậm chí không đọc đối tượng này.

(g) Đọc bẩn xảy ra khi một giao dịch nào đó đọc một đối tượng mà đối tượng này đang được thay đổi bằng một giao dịch chưa thành công khác.

(h) Đọc không thể lặp lại xảy ra khi một giao dịch nào đó không thể đọc cùng một giá trị đối tượng nhiều hơn một lần, thậm chí giao dịch này không được phép thay đổi giá trị. Giả sử giao dịch T2 thay đổi giá trị của đối tượng A – đối tượng đang được đọc bằng một giao dịch T1 trong khi T1 vẫn đang trong quá trình xử lý. Nếu T1 cố gắng đọc giá trị A một lần nữa, nó sẽ có một kết quả khác, mặc dù nó không thay đổi A.

(i) Lịch trình có khả năng tuần tự trên một tập S của các giao dịch là một lịch trình mà ảnh hưởng của nó trên bất kỳ minh họa cơ sở dữ liệu nhất quán nào đều giống với lịch trình tuần tự hoàn toàn trên tập các giao dịch thành công trong S.

(j) Lịch trình có khả năng phục hồi là một lịch trình mà trong đó một giao dịch có thể được thành công chỉ sau khi tất cả các giao dịch khác đọc nó đã thành công.

(k) Lịch trình tránh hủy bỏ chồng là một trong số các giao dịch chỉ đọc những thay đổi của các giao dịch đã thành công. Một lịch trình như vậy không chỉ có khả năng phục hồi, việc hủy bỏ một giao dịch có thể được hoàn thành mà không hủy bỏ chồng các giao dịch khác.

3. Strict 2PL là một giao thức khóa được sử dụng rộng rãi nhất trong đó 1) Một giao dịch yêu cầu một khóa chia sẻ/ độc quyền trên một đối tượng trước khi nó đọc/ sửa đối tượng đó. 2) Tất cả các khóa mà giao dịch nắm bắt được giải phóng khi giao dịch đó thành công.

4. Vấn đề dữ liệu ma là tình trạng mà một giao dịch nào đó truy cập đến một tập các đối tượng trong hai lần và nhìn thấy các kết quả khác nhau, mặc dù bản thân nó không thay đổi bất kỳ đối tượng nào và tuân thủ giao thức strick 2PL. Vấn đề này thường nảy sinh trong các cơ sở dữ liệu động nơi mà một giao dịch không thể được giả sử rằng nó được khóa tất cả các đối tượng theo một loại yêu cầu (ví dụ, tất cả các thủ thủ có xếp hạng (rank) là 1; những thủy thủ mới có rank bằng 1 có thể được thêm vào bởi giao dịch thứ hai sau khi một giao dịch đã khóa tất cả các đối tượng liên quan). Nếu một tập các đối tượng cơ sở dữ liệu được cố định lại và chỉ những giá trị của các đối tượng này có thể được thay đổi, thì vấn đề dữ liệu ma không thể xảy ra vì giao dịch khác không thể thêm các đối tượng mới vào cơ sở dữ liệu.

Xem xét những thao tác sau được T1 thực hiện trên đối tượng X và Y của cơ sở dữ liệu:

R(X), W(X), R(Y), W(Y)

  1. Cung cấp một ví dụ của giao dịch T2 mà khi chạy đồng thời với T1 nó sẽ ảnh hưởng tới T1.
  2. Sử dụng Strict 2PL như thế nào sẽ tránh được sự ảnh hưởng giữa hai giao dịch.
  3. Strict 2PL được sử dụng trong rất nhiều hệ thống cơ sở dữ liệu. Cung cấp hai lý do khiến nó được sử dụng rộng rãi.

Dành cho độc giả

Xem xét một cơ sở dữ liệu có hai đối tượng X và Y và giả sử rằng có hai giao dịch T1 và T2. Giao dịch T1 đọc X và Y, sau đó ghi lên X. Giao dịch T2 đọc X và Y, sau đó ghi lên cả X và Y.

  1. Cho một ví dụ về một lịch trình chứa các thao tác của T1 và T2 trên đối tượng X và Y có thể dẫn đến xung đột ghi-đọc.
  2. Cho một ví dụ về một lịch trình chứa các thao tác của T1 và T2 trên đối tượng X và Y có thể dẫn đến xung đột đọc-ghi.
  3. Cho một ví dụ về một lịch trình chứa các thao tác của T1 và T2 trên đối tượng X và Y có thể dẫn đến xung đột ghi-ghi.
  4. Với mỗi lịch trình trong ba lịch trình trên, chỉ ra rằng Strict 2PL không cho phép lịch trình nào.

Trả lời 3Câu trả lời cho mỗi câu hỏi như sau:

1. Lịch trình trong một xung đột ghi-đọc:

T2:R(X), T2:R(Y), T2:W(X), T1:R(X) ...

T1:R(X) ở đây là một phép đọc bẩn.

2. Lịch trình trong một xung đột đọc-ghi:

T2:R(X), T2:R(Y), T1:R(X), T1:R(Y), T1:W(X) ...

Bây giờ T2 sẽ có một phép đọc không có khả năng lặp trên X.

3. Lịch trình trong một xung đột ghi-ghi:

T2:R(X), T2:R(Y), T1:R(X), T1:R(Y), T1:W(X), T2:W(X) ...

Bây giờ T2 viết đè lên những dữ liệu chưa thành công.

4. Strict 2PL giải quyết những xung đột này như sau:

(a) Trong S2PL, T1 không thể có một khóa chia sẻ trên X vì T2 sẽ không nắm khóa độc quyền trên X. Vì thế, T1 sẽ không phải đợi cho đến khi T2 kết thúc.

(b) Ở đây T1 không thể có một khóa độc quyền trên X bởi vì T2 sẽ không nắm giữ khóa chia sẻ hoặc khóa độc quyền trên X.

(c) Như trên.

Chúng tôi gọi một giao dịch chỉ đọc các đối tượng trong cơ sở dữ liệu là giao dịch chỉ-đọc, ngược lại giao dịch này sẽ được gọi là giao dịch đọc-ghi. Trả lời tóm tắt những câu hỏi sau:

  1. Lock thashing là gì và nó xảy ra khi nào?
  2. Cái gì xảy ra với throughput của hệ cơ sở dữ liệu nếu số lượng các giao dịch đọc-ghi tăng lên?
  3. Cái gì xảy ra với throughput của hệ cơ sở dữ liệu nếu số lượng các giao dịch chỉ-đọc tăng lên?
  4. Trình bày ba cách tinh chỉnh hệ thống của bạn để làm tăng throughput giao dịch.

Dành cho độc giả

Giả sử rằng DBMS nhận ra sự tăng lên (tăng một đối tượng có giá trị nguyên lên 1) và giảm đi như là các thao tác (ngoài các thao tác đọc và ghi thông thường). Một giao dịch làm tăng giá trị một đối tượng nào đó mà không cần biết giá trị của đối tượng đó; tăng và giảm này được gọi là viết mù. Ngoài hai khóa chia sẻ và khóa độc quyền, hai khóa đặc biệt khác được hỗ trợ: Một đối tượng nào đó phải được khóa theo phương thức I trước khi tăng nó lên và khóa theo phương thức D trước khi giảm nó. Khóa I tương thích với các khóa I và D khác trên cùng đối tượng, nhưng không tương thích với các khóa S (khóa chia sẻ) và X (khóa độc quyền).

  1. Minh họa việc sử dụng các khóa I và D như thế nào để có thể tăng được sự tương tranh. (Chỉ ra một lịch trình sử dụng các khóa S và X được Strict 2PL cho phép. Giải thích cách sử dụng các khóa I và D như thế nào để có thể có nhiều thao tác được thực hiện xen kẽ, trong khi vẫn phải thỏa mãn Strict 2PL).
  2. Giải thích cách Strick 2PL đảm bảo sự tuần tự khi có sự hiện diện của các khóa I và D.

Câu trả lời cho mỗi câu hỏi như sau:

1. Coi hai giao dịch sau như là ví dụ:

T1: Tăng A, Giảm B, Đọc C;

T2: Tăng B, Giảm A, Đọc C.

Nếu chỉ sử dụng giao thức 2PL, tất cả các thao tác là phiên bản của việc viết mù, chúng phải thực hiện các khóa độc quyền trên các đối tượng. Theo giao thức 2PL, T1 có một khóa độc quyền trên A, nếu T2 bây giờ có một khóa độc quyền trên B, thì ở đây sẽ có một khóa chết. Ngay cả khi T1 đủ nhanh để đạt được khóa độc quyền trên B trước, thì bây giờ T2 sẽ không bị đóng khối cho đến khi T1 kết thúc. Ở đây sẽ có ít xung đột. Nếu khóa I và D được sử dụng, vì I và D là tương thích, T2 đạt được khóa I trên A, và khóa D trên B; T2 có thể vẫn đạt được khóa I trên B, khóa D trên A; cả hai giao dịch có thể được thực thi xen kẽ.

2. Các cặp thao tác xung đột là:

RW, WW, WR, IR, IW, DR, DW

Chúng ta biết rằng strict 2PL đề nghị ba cặp đầu tiên là cùng thứ tự trong một số lịch trình tuần tự. Chúng ta có thể cũng chỉ ra rằng mặc dù có sự hiện diện của các khóa I và D, strict 2PL cũng yêu cầu 4 cặp sau là cùng thứ tự trong một số lịch trình tuần tự. Nghĩ về khóa I (hoặc D) trong tình huống này như là khóa độc quyền, vì khóa I(D) không tương thích với các khóa S và X theo bất kỳ cách nào (tức là, không thể có khóa S hoặc X nếu một giao dịch khác đang có một khóa I hoặc khóa D). Vì thế sự tuần tự được đảm bảo.

Trả lời những câu hỏi sau: SQL hỗ trợ bốn mức cô lập và hai phương thức truy cập, như vậy có tổng số 8 sự kết hợp giữa mức cô lập và phương thức truy cập. Mỗi sự kết hợp định nghĩa một lớp các giao dịch; những câu hỏi sau dựa vào 8 lớp này:

  1. Xem xét bốn mức cô lập của SQL. Trình bày về những hiện tượng sau có thể xảy ra ở mỗi mức cô lập: đọc bẩn, đọc không thể lặp lại, vấn đề phantom.
  2. Với mỗi mức cô lập, nêu những ví dụ về giao dịch có thể chạy an toàn ở mức cô lập này. Phương thức truy cập của giao dịch quan trọng vì sao?

Dành cho độc giả

Xem xét lược đồ quan hệ dùng để đăng ký học tại trường đại học như sau:

Student(snum: integer, sname: string, major: string, level: string, age: integer)

Class(name: string, meets_at: time, room: string, fid: integer)

Enrolled(snum: integer, cname: string)

Faculty (fid: integer, fname: string, deptid: integer)

Ý nghĩa của các quan hệ này rất dễ hiểu; ví dụ, Enrolled (Đăng ký) có một bản ghi ứng với mỗi cặp student-class (sinh viên – lớp) để ghi lại thông tin sinh viên đăng ký lớp học.

Với mỗi giao dịch sau đây, trình bày về mức cô lập bạn sẽ sử dụng và giải thích vì sao bạn chọn nó?

  1. Một sinh viên nào đó được (xác định bằng snum) đăng ký lớp học có tên là ‘Introduction to Database Systems’
  2. Thay đổi đăng ký của một sinh viên nào đó (xác định bằng snum) từ lớp này sang một lớp khác.
  3. Gán một khoa mới (xác định bằng fid) cho lớp có số lượng sinh viên ít nhất.
  4. Với mỗi lớp, chỉ ra số lượng sinh viên đăng ký trong lớp đó.

Câu trả lời cho mỗi câu hỏi như sau:

1. Bởi vì chúng ta đang thêm một dòng mới vào bảng Enrolles, nên chúng ta không cần bất kỳ khóa nào trên các dòng đang tồn tại. Vì thế chúng ta sẽ sử dụng READ UNCOMMITTED.

2. Vì chúng ta đang cập nhật một dòng đang tồn tại trong bảng Enrolles, chúng ta cần một khóa độc quyền trên dòng đang được cập nhật. Vì thế chúng ta sẽ sử dụng READ COMMITTED.

3. Để ngăn chặn những giao dịch thực hiện việc thêm và cập nhật bảng Enrolles trong khi chúng ta đang đọc nó (tránh xảy ra trường hợp gặp phải dữ liệu ma), chúng ta sẽ cần sử dụng SERIALIZABLE.

4. Như trên.

Xem xét lược đồ sau:

Suppliers(sid: integer, sname: string, address: string)

Parts(pid: integer, pname: string, color: string)

Catalog(sid: integer, pid: integer, cost: real)

Quan hệ Catalog (Danh mục) liệt kê giá các sản phẩm (Parts) của các nhà cung cấp (Suppliers).

Với mỗi giao dịch sau đây, trình bày về mức cô lập bạn sẽ sử dụng và giải thích vì sao bạn chọn nó?

  1. Giao dịch thêm một sản phẩm mới vào danh mục.
  2. Giao dịch tăng giá một sản phẩm của một nhà cung cấp.
  3. Giao dịch xác định tổng số mặt hàng của một nhà cung cấp nào đó.
  4. Giao dịch chỉ ra ứng với mỗi sản phẩm, nhà cung cấp nào cung cấp sản phẩm này với giá thấp nhất.

Dành cho độc giả

Xem xét cơ sở dữ liệu có lược đồ quan hệ như sau:

Suppliers(sid: integer, sname: string, address: string)

Parts(pid: integer, pname: string, color: string)

Catalog(sid: integer, pid: integer, cost: real)

Quan hệ Catalog (Danh mục) liệt kê giá các sản phẩm (Parts) của các nhà cung cấp (Suppliers).

Xem xét ba giao dịch T1, T2 và T3; T1 luôn có mức cô lập là SERIALIZABLE. Chúng ta cho T1 và T2 chạy đồng thời và sau đó cho T1 và T2 chạy đồng thời. Nêu một minh họa cơ sở dữ liệu và các câu lệnh SQL cho T1 và T2 mà kết quả của việc chạy T2 ở mức cô lập đầu tiên khác với chạy T2 ở mức cô lập thứ hai. Đồng thời xác định lược đồ chung của T1 và T2 và giải thích vì sao kết quả này khác nhau.

1. SERIALIZABLE versus REPEATABLE READ.

2. REPEATABLE READ versus READ COMMITTED.

3. READ COMMITTED versus READ UNCOMMITTED.

Câu trả lời cho mỗi câu hỏi như sau

1. Giả sử có một minh họa cơ sở dữ liệu của bảng Catalog và các câu lệnh SQL như sau:

SELECT *

FROM Catalog C

WHERE C.cost < 100

EXCEPT

(SELECT *

FROM Catalog C

WHERE C.cost < 100 )

INSERT INTO catalog (sid, pid, cost)

VALUES (99, 38, 75.25)

Khi chúng ta sử dụng SERIALIZABLE, chúng ta sẽ hy vọng rằng câu lệnh SQL đầu tiên không trả về gì cả. Nhưng thay vì đó chúng ta sử dụng REPEATABLE READ, thì vấn đề dữ liệu ma có thể sẽ xảy ra khi thêm một bộ dữ liệu mới vào bảng này, vì thế câu lệnh SQL đầu tiên trả về kết quả sai (trong trường hợp có một bộ dữ liệu được thêm bằng câu SQL thứ hai).

2. Giả sử rằng cùng một minh họa dữ liệu như trên và các câu lệnh SQL chỉ ra như sau:

UPDATE Catalog

SET cost = cost * 0.95

WHERE sid = 31

UPDATE Catalog

SET cost = cost * 0.95

WHERE sid = 31

Khi chúng ta sử dụng READ COMMITTED trên các câu lệnh SQL trên, việc đọc không thể lặp lại có thể xảy ra dẫn đến một giá trị không đúng được gán vào chi phí. Nhưng vấn đề này không thể xảy ra khi chúng ta sử dụng REPEATABLE READ.

3. Giả sử rằng cùng một minh họa dữ liệu như trên và các câu lệnh SQL chỉ ra như sau:

(giả sử READ UNCOMMITTED có thể viết tới cơ sở dữ liệu):

UPDATE Catalog

SET cost = cost * 0.95

SELECT C.sid, C.pid

FROM Catalog C

WHERE C.cost = 36.22

Khi chúng ta sử dụng READ UNCOMMITTED trên các câu lệnh SQL trên, việc đọc bẩn có thể xảy ra vì câu lệnh SQL đầu tiên có thể được kết thúc trong khi câu lệnh thứ nhất đang đọc. Nhưng vấn đề này không thể xảy ra khi chúng ta sử dụng READ UNCOMMITTED.