Giao thức khóa đa hạt dưới đây đảm bảo tính khả tuần tự. Mỗi giao tác T có thể khóa một nút N theo các quy tắc sau [1,2]:
(1) Hàm tương thích khóa (dựa vào hình 2.8) phải được kiểm chứng (2) Gốc của cây phải được chốt đầu tiên, và có thể được chốt ở bất
(3) Một nút N có thể được khóa bởi T ở phương thức S hoặc IS chỉ khi cha của N hiện đang bị khóa bởi T ở hoặc phương thức IX hoặc phương thức IS.
(4) Một nút N có thể được khóa bởi T ở phương thức X, SIX hoặc IX chỉ khi cha của N hiện đang bị khóa ở hoặc phương thức IX hoặc phương thức SIX
(5) T có thể khóa một nút chỉ khi trước đó nó chưa mở khóa một nút nào (tuân theo giao thức 2PL).
(6) T có thể mở khóa một nút N chỉ khi không còn nút con nào của N hiện đang bị khóa bởi T
Ta thấy rằng, giao thức đa hạt yêu cầu các khóa được nhận theo thứ tự Top-Down và được mở theo thứ tự Bottom-Up.
CHƢƠNG 3: QUẢN LÝ GIAO TÁC TRONG SQL
Chương này trình bày một số kỹ thuật quản lý giao tác trong SQL thông qua một số ví dụ cụ thể. Khi làm việc với SQL trong chế độ tương tác, mỗi lênh SQL là một giao tác và giao tác kết thúc cùng với lệnh. Tuy nhiên khi viết chương trình với SQL nhúng hoặc chương trình sử dụng SQL/CLI hoặc JDBC, một giao tác là một tập lệnh và bắt đầu với lệnh SQL START TRANSACTION. Có hai cách để kết thúc một giao tác:
1. Lệnh SQL COMMIT: giao tác kết thúc một cách thành công. Mọi thay đổi của cơ sở dữ liệu do các lệnh trong giao tác gây ra sẽ được lưu giữ lại. Trước khi thực hiện lệnh COMMIT, các thay đổi của cơ sở dữ liệu là “không nhìn thấy được” đối với các giao tác khác
2. Lệnh SQL ROLLBACK: Giao tác kết thúc không thành công (hay bị bỏ dở). Mọi thay đổi do các lệnh SQL của giao tác gây ra là không thực hiện được (tức là chúng được ROLLBACK), và không còn xuất hiện trong CSDL.
Ví dụ, xét hàm transfer( ) thực hiện chuyển một khoản tiền từ tài khoản này sang tài khoản khác. Đây là một chương trình SQL trong C++, để đơn giản ta bỏ qua các lệnh C như các lệnh đưa vào, đưa ra...
1) EXEC SQL BEGIN DECLARE SECTION; 2) int acct1, acct2; /* 2 toài khoản*/
3) int balance1; /*Số lượng tiền trong tài khoản thứ nhất*/ 4) int amount; /*Số tiền cần chuyển*/
5) EXEC SQL END DECLARE SECTION; 6) void transfer() {
7) /* đoạn mã C để nhắc người dùng nhập vào tài khoản 1 và 2 và số tiền cần chuyển, theo các biến acct1, acct2 và amount*/ 8) EXEC SQL SELECT balance INTO :balance1
9) FROM Accounts
10) WHERE AcctNo = :acct1; 11) if (balance1 > amount) {
12) EXEC SQL UPDATE Accounts
13) SET balance = balance + :amount
14) WHERE acctNo = :acct2;
15) EXEC SQL UPDATE Accounts
16) SET balance = balance - :amount
17) WHERE acctNo = :acct1;
}
18) else /* Đoạn mã C in ra thông báo cho người dùng biết là không có đủ tiền trong tài khoản để thực hiện chuyển tiền */
}
Nếu coi chương trình này như một giao tác thì ta đưa thêm một dòng lệnh EXEC SQL START TRANSACTION vào trước dòng 8, khi bắt đầu đọc số dư của tài khoản thứ nhất. Nếu kiểm tra ở dòng 11 là đúng và thực hiện chuyển tiền thì chúng ta cần ghi lại các thay đổi đã được làm thi ta đưa thêm lệnh EXEC SQL COMMIT vào sau lệnh ở dòng 17. Nếu kiểm tra ở dòng 11 là sai (tức là không có đủ tiền để chuyển) thì giao tác phải được huỷ bỏ. Như vậy, ta sẽ thêm vào lệnh EXEC SQL ROLLBACK sau dòng 18. Với việc đưa vào các dòng lệnh như vậy, những thay đổi trong cơ sở dữ liệu (tài khoản 1 được trừ tiền đi, tài khoản 2 được cộng thêm tiền vào) sẽ được lưu vào cơ sở dữ liệu.
Bây giờ chúng ta xét một số kỹ thuật trong việc điều khiển tính tuần tự, tính nguyên tử, .. trong SQL.
3.1 Xếp hàng thứ tự
Giả sử chúng ta có một chương trình SQL nhúng với một hàm chooeSeat( ) dùng để đọc một quan hệ về các chuyến bay (quan hệ FLIGHTS) và các chỗ sẵn có, tìm một chỗ cụ thể và làm cho nó trở thành bị chiếm. Giả thiết quan hệ FLIGHTS có các thuộc tính fltNum (số chuyến bay), fltDate (ngày bay), fltseat (chỗ) và occupied là một trường logic, có giá trị là TRUE
muốn mua một chỗ trên một chuyến bay, khách hàng cần cho biết số chuyến bay, ngày bay và số ghế. Chương trình thực hiện bằng cách trước hết kiểm tra số ghế trên chuyến bay và ngày bay mà khách hàng yêu cầu xem có rỗi không. Nếu rỗi thì làm cho nó trở thành bị chiếm (occupied = TRUE) và thông báo cho khách hàng, nếu chỗ đã bị chiếm thì cũng thông báo cho khách hàng để chọn chỗ khác.
Chương trình như sau:
1) EXEC SQL BEGIN DECLARE SECTION; 2) int flight; /* số chuyến bay*/
3) char date[10]; /*ngày bay và theo định dạng trong SQL*/ 4) char seat[3]; /*2 số và 1 ký tự mô tả một chỗ ngồi*/ 5) int occ; /* 1 biến logic để thông báo nếu chỗ ngồi xuất hiện*/
6) EXEC SQL END DECLARE SECTION; 7) void ChooseSeat() {
8) /* đoạn mã C để nhắc người dùng nhập vào số chuyến bay ngày bay, chỗ ngồi và lưu 3 giá trị này trong 3 biến*/ 9) EXEC SQL SELECT occupied INTO :occ
10) FROM Flights
11) WHERE fltNum = :flight AND fltDate = :date
AND fltSeat = :seat;
12) if (!occ) {
13) EXEC SQL UPDATE Flights
14) SET occupied = TRUE
15) WHERE fltNum = :flight
AND fltDate = :date AND fltSeat = :seat; 16) /* đoạn mã C và SQL để ghi lại chỗ ngồi
và thông tin người dùng đã chỉ định */ }
17) else /* Đoạn mã C thông báo cho người dùng là các lựa
chọn đó không còn và yêu cầu lựa chọn chỗ ngồi khác */ }
Chương trình này có thể được hai (hoặc nhiều hơn) khách hàng thực hiện một cách đồng thời. Giả sử rằng hai đại lý đang cố gắng mua cùng một chỗ
trên cùng chuyến bay và ngày bay một cách đồng thời. Cả hai đi đến dòng 9 cùng một lúc và cả hai bản sao biến cục bộ occ nhận giá trị FALSE, như vậy chỗ ngồi hiện tại đang trống. Tại dòng 12, mỗi thực hiện của chooseSeat( ) quyết định đổi occ thành TRUE và như vậy là làm cho chỗ trở thành bị chiếm. Các cập nhật này thực hiện đồng thời và mỗi thực hiện nói với khách hàng rằng (ở dòng 16) chỗ thuộc về họ. Như vậy, một chỗ được bán cho hai khách hàng!
Từ ví dụ trên ta thấy rằng hai giao tác được thực hiện đúng đắn nhưng kết quả cuối cùng là không đúng. Để làm cho việc thực hiện các giao tác đồng thời là đúng đắn, SQL có nhiều cơ cấu để phục vụ cho việc xếp hàng thứ tự sự thực hiện của hai thể hiện hàm. Một trong những cơ cấu đó là sử dụng cặp lệnh LOCK và UNLOCK. Lệnh UNLOCK được đặt ở dòng đầu của hàm, khi một giao tác thực hiện hàm, nó đối lệnh UNLOCK thành LOCK và sau khi thực hiện xong, lệnh LOCK lại được đổi thành UNLOCK. Như vậy giao tác nào thực hiện hàm trước sẽ bắt giao tác sau phải chờ đợi cho đến khi nó thực hiện hàm xong. Kỹ thuật này làm cho các giao tác được thực hiện theo thứ tự (khi cùng thực hiện các thao tác như nhau) làm tránh các sai sót trên.
3.2 Tính nguyên tử
Xét lại ví dụ về chương trình chuyển tiền từ tài khoản này sang tài khoản kia ở trên. Giả sử có một hư hỏng nào đó xẩy ra ở sau dòng 14. Hư hỏng có thể là do một sự cố máy tính hoặc mạng.... Khi đó cơ sở dữ liệu được đặt ở trạng thái tiền đã được chuyển vào tài khoản thứ hai nhưng chưa được trừ đi khỏi tài khoản thứ nhất, như vậy sẽ gây ra việc ngân hàng bị mất tiền!
Để giải quyết điều đó, SQL đưa vào các lệnh START TRANSACTION, COMMIT, ROLLBACK và đặt vào các vị trí như đã nói ở trên. Nếu có một hư hỏng xẩy ra sau dòng 14, giao tác vẫn chưa đi đến lệnh SQL COMMIT,
nghĩa là những thay đổi của cơ sở dữ liệu vẫn chưa được lưu giữ. Như vậy kết quả của lệnh UPDATE thứ nhất (chuyển tiền) vẫn chưa được lưu vào cơ sở dữ liệu. Do giao tác bị hỏng nên hệ thống sẽ căn cứ vào thông tin trong file log mà khôi phục lại giá trị cho tài khoản thứ hai, việc mất tiền là không xảy ra.
Giao tác chỉ đọc
Chúng ta đã biết, các giao tác chỉ đọc không làm thay đổi trạng thái của cơ sở dữ liệu và do đó chúng có thể được thực hiện song song với các giao tác khác. Xét ví dụ cụ thể sau:
Giả sử chúng ta muốn viết một hàm đọc các dữ liệu để xác định xem một chỗ (trên máy bay) có rỗi hay không. Hàm này sẽ hoạt động giống như từ dòng 1 đến dòng 11 của hàm chooseSeat( ). Ta có thể thực hiện nhiều lần gọi hàm này cùng một lúc mà không sợ làm hại đến cơ sở dữ liệu. Như vậy ta cần báo với hệ thống SQL rằng đây là một giao tác chỉ đọc. SQL cho phép chúng ta báo với hệ thống một giao tác chỉ đọc bằng lệnh:
SET TRANSACTION READ ONLY;
Lệnh này phải được thực hiện trước khi giao tác bắt đầu. Ví dụ, nếu hàm của chúng ta bao gồm các dòng từ dòng 1 đến dòng 11 của chương trình chọn chỗ thì ta có thể khai báo nó là chỉ đọc bằng cách đặt
EXEC SQL SET TRANSACTION READ ONLY; ngay trước dòng 9, nơi bắt đầu giao tác.
Vấn đề Dirty Read
Dữ liệu rác là các dữ liệu được ghi bằng một giao tác nhưng còn chưa được lưu giữ (commited). Việc đọc một dữ liệu rác gọi là dirty read. Dirty read có thể gây ra các sai sót nghiêm trọng mà cũng có thể không có mấy ý nghĩa. Xét hai trường hợp sau đây:
Dirty read không gây nguy hiểm
Giả sử ta có một thay đổi trên hàm chooseSeat( ) như sau:
1.Chúng ta tìm thấy một chỗ rỗi và đăng ký nó bằng cách làm cho occ trở thành TRUE đối với chỗ đó.
2.Hỏi khách hàng có đồng ý với chỗ đó không. Nếu khách hàng đồng ý, ta giữ lại (COMMIT). Nếu không, ta giải phóng chỗ bằng cách làm cho occ thành FALSE và lặp lại bước 1 để lấy chỗ khác.
Nếu hai giao tác đang thực hiện thuật toán tại cùng một thời điểm, một giao tác có thể đăng ký chỗ S (ở bước 1) và sau đó giải phóng nó do khách hàng không đồng ý. Nếu giao tác thứ hai thực hiện bước 1 tại thời điểm khi chỗ S được đánh dấu là đã đăng ký (nhưng chưa commit), khách hàng ứng với giao tác này sẽ không được lựa chọn để lấy chỗ S. Lý do là do giao tác thứ hai đã đọc dữ liệu rác (giá trị occ). Tuy có sai sót như vậy nhưng nó không gây nguy hiểm lắm, bởi vì khách hàng thứ hai có thể không chọn được chỗ S nhưng khi chạy lại giao tác lần sau chỗ đó vẫn được bán. Trong những trường hợp như vậy, việc cài đặt hàm chooseSeat như vậy cho phép đọc dữ liệu rác. Điều đó làm giảm thời gian xử lý trung bình đối với các yêu cầu mua vé
SQL cho phép chúng ta chỉ ra rằng các dirty read là chấp nhận được với một giao tác cho trước bằng cách sử dụng cặp lệnh:
1. SET TRANSACTION READ WRITE;
2. ISOLATION LEVEL READ UNCOMMITED;
Dòng lệnh 1 có nghĩa là giao tác có thể ghi dữ liệu. Dòng lệnh 2 khi bó rằng giao tác có thể chạy với “isolation level” read-uncommited, nghĩa là giao tác được cho phép thực hiện các dirty read.
Dirty read có thể gây nguy hiểm
Giả sử chúng ta thay thế việc chuyển khoản trong chương trình transfer() ở trên bằng một chương trình thực hiện các dãy bước như sau:
1. Thêm tiền vào tài khoản thứ hai 2. Kiểm tra tài khoản thứ nhất:
a) Nếu không có đủ tiền: lấy tiền ra khỏi tài khoản thứ hai và dừng b) Nếu có đủ tiền: trừ số tiền ra khỏi tài khoản thứ nhất và dừng. Nếu chương trình này được thực hiện một cách có thứ tự thì kết quả của nó cũng hoàn toàn giống như chương trình transfer( ) ở trên. Trong trường hợp thực hiện đan xen có thể dẫn đến nguy hiểm (cho kết quả không mong đợi). Giả sử giao tác cho phép dirty-read và ta có tình huống sau: Có ba tài khoản A1, A2, A3 với số tiền 100$, 200$ và 300$ tương ứng. Giả sử rằng giao tác T1 thực hiện chương trình P để chuyển 150$ từ A1 đến A2. Cùng một thời gian, giao tác T2 chạy chương trình P để chuyển 250$ từ A2 đến A3. Có khả năng xẩy ra các sự kiện sau:
1.T2 thực hiện bước 1 và them 250$ vào A3 và bây giờ A3 có 550$ 2.T1 thực hiện bước 1 và thêm 150$ vào A2 và bây giờ A2 có 350$ 3.T2 thực hiện kiểm tra của bước 2 và thấy rằng A2 có đủ tiền (350$)
để cho phép chuyển 250$ từ A2 sang A3.
4.T1 thực hiện kiểm tra của bước 2 và thấy rằng T1 không có đủ tiền (100$) để cho phép chuyển 150$ từ A1 sang A2.
5.T2 thực hiện tiếp bước 2, nó trừ đi 250$ từ A2 và bây giờ A2 có 100$ và kết thúc
6.T1 thực hiện tiếp bước 2, nó trừ đi 150% từ A2 và bây giờ A2 có – 50$ và kết thúc.
Tổng số tiền không thay đổi, trong ba tài khoả vẫn có 600$ nhưng do T2 đọc dữ liệu bẩn ở bước 3 trong 6 bước trên, ta không bảo vệ được việc một tài khoản trở nên âm (vi phạm quy chế của ngân hàng).
Kết quả thực hiện đồng thời hai giao tác T1, T2 như trên dẫn đến sai lầm nghiêm trọng. Để ngăn ngừa những trường hợp như vậy, ta phải sử dụng các công cụ của SQL để làm cho các giao tác phải thực hiện theo thứ tự.
Ngoài việc SQL có thể chỉ ra một giao tác là có thể dirty read, nó còn cho phép chỉ ra một giao tác không được dirty read bằng cách dùng lệnh
SET TRANSACTION ISOLATION LEVEL READ COMMITED và chỉ ra một giao tác không được đọc các bộ “ảo - phantom” bằng cách dùng lệnh
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ Các read uncommited, read commited, reapeatable read và xếp hàng có thứ tự được gọi là các mức cô lập của các giao tác trong SQL.
KẾT LUẬN
Trong luận văn này, tôi đã trình bày khái niệm về giao tác, tầm quan trọng của việc quản lý các giao tác đồng thời và các loại lịch biểu tương ứng với các giao tác và chỉ ra lịch biểu nào có thể khôi phục được các giao tác mỗi khi giao tác bị hỏng.
Việc quản lý các giao tác được thực hiện bằng các thuật toán điều khiển đồng thời và đảm bảo sự loại trừ lẫn nhau trong việc truy cập các mục dữ liệu hoặc sự đồng bộ của các giao tác nhằm đảm bảo việc truy cập dữ liệu đúng đắn và an toàn. Các thuật toán này được trình bày tương đối đầy đủ ở chương 2.
Trong chương 2, tôi trình bày các kỹ thuật được cụ thể hóa trong ngôn ngữ SQL. Các thực nghiệm với hệ thống cho thấy hệ thống đáp ứng các yêu cầu, tính năng được đặt ra. Hoạt động và khả năng phục hồi dữ liệu của hệ thống tốt. Mặc dù việc đáp ứng các tiêu chí bảo mật, khả năng bảo trì là rất khó hoặc có thể nói là không thể đo kiểm một cách đáng thuyết phục, với những hiểu biết hiện tại, hệ thống vẫn được coi là hoàn toàn đáp ứng các yêu cầu đặt ra. Trong phạm vi nào đó, việc triển khai phải được giới hạn ở các tính năng tối thiểu vì sẽ là không khả thi hoặc vô nghĩa để bao trùm tất cả các tùy chọn có thể trong phạm vi luận văn này
Trong quá trình hoàn thành đề tài, tôi nhận thấy rằng việc nghiên cứu lý thuyết về quản lý giao tác và thực hành các kỹ thuật quản lý giao tác với các ngôn ngữ lập trình cụ thể là một việc có ý nghĩa, một lĩnh vực nghiên cứu rộng lớn và có nhiều triển vọng. Nó đảm bảo sự đúng đắn cho cơ sở dữ liệu