24/05/2018, 17:00

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 ...

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. (Chúng tôi trình bày chi tiết về động cơ của việc xen vào này trong Phần 3.1). 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ố.

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.

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).

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 2 có thể được thực hiện như Hình 3. 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 Phần 3.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 17.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 (như trình bày trong Phần 17.1).

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à:

  • 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à:

  • 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 17.2.

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.

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 17.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 17.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 (xem Phần 17.5.1) 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. Chúng tôi trình bày về việc khóa chỉ số chi tiết trong Phần 17.5.1.

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.

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

0