Sunday, March 15, 2009

Rollback một phần Transaction

Đặt vấn đề :
Thông thường khi chúng ta rollback một transaction thì toàn bộ những thao tác đã thực hiện trong transaction đó đều bị hủy bỏ. Tuy nhiên trong một số trường hợp nhất định chúng ta có nhu cầu chỉ hủy bỏ một số thao tác nào đó mà thôi (các thao tác còn lại không bị hủy bỏ). Tuy nhu cầu này ít khi phát sinh nhưng Microsoft SQLServer có hỗ trợ điều ấy.
Các câu lệnh và cú pháp
Save tran stampName
Lệnh này gắn nhãn tại một vị trí nhất định trong transaction với tên nhãn là stampName. Tên nhãn này có thể là một biến chuỗi hay một hằng chuỗi. Khi là hằng chuỗi, tên nhãn không được để giữa cặp nháy đơn.
Ví dụ :
Đúng :
Begin tran

Save tran nhan_1

Declare @nhan varchar(10)
Set @nhan = ‘nhan_2’
Save tran @nhan

Sai :
Save tran ‘nhan_1’
Rollback tran stampName
Lệnh này rollback một phần của transaction. Các thao tác bị rollback là các thao tác nằm trên câu lệnh “Rollback tran stampName” và nằm dưới câu lệnh “Save tran stampName” với tên nhãn tương ứng.
Ví dụ :
Begin tran
--Thao tác 1
--Thao tác 2
Save tran nhan_1
--Thao tác 3
--Thao tác 4
Rollback tran nhan_1
--Thao tác 5
--Thao tác 6
Commit tran / Rollback tran

Trong ví dụ này, khi chạy đến dòng lệnh “Rollback tran nhan_1”, các thao tác 3 và 4 sẽ bị hủy bỏ. Trong khi ấy các thao tác 1 và 2 thì không bị hủy bỏ. Sau đó SQLServer sẽ vẫn tiếp tục làm nốt các thao tác 5 và 6.
Lưu ý : Câu lệnh “rollback tran nhan_1” chỉ đơn thuần làm công việc undo thao tác 3 và 4, nó không hề làm chấm dứt transaction như câu lệnh “rolback tran” thông thường. Nói các khác, sau khi hủy bỏ thao tác 3 và 4, transaction vẫn tiếp tục chạy cho đến khi gặp câu lệnh “rollback tran” hoặc “commit tran” thì mới kết thúc. Trong ví dụ trên, nếu câu lệnh cuối là “commit tran” thì các thao tác 1, 2, 5 và 6 sẽ được lưu bền vững vào cơ sở dữ liệu. Ngược lại, nếu câu lệnh cuối là “rollback tran” thì các thao tác này sẽ bị hủy bỏ.
Liên hệ các tính chất của transaction
Khi cho transaction rollback một phần, ta cần liên hệ chặt chẽ với các tính chất của transaction như sau :
Tính nguyên tố (Atomicity ) : Việc rollback một phần transaction chắc chắn là vi phạm tính nguyên tố. Nếu sử dụng rollback một phần nghĩa là chấp nhận vi phạm này.
Tính vững bền (Consistency): Việc rollback một phần không liên can gì đến tính vững bền vì nó không làm ảnh hưởng đến phạm vi của transaction. Bất kỳ thao tác nào nằm trong phạm vi transaction mà đã được commit thì sẽ được SQLServer bảo đảm lưu bền vững vào CSDL.
Tính nhất quán (Durability) : Sau khi thực hiện transaction, CSDL vẫn phải ở trong trạng thái nhất quán, nghĩa là không vi phạm các ràng buộc toàn vẹn. Do đó, người viết transaction khi quyết định sử dụng rollback một phần thì phải xem xét kỹ các ràng buộc toàn vẹn để bào đảm dù xảy ra rollback một phần thì cũng không vi phạm ràng buộc toàn vẹn nào.
Tính cô lập (Isolation) : Rollback một phần không ảnh hưởng gì đến mức cô lập của transaction nhưng ảnh hưởng rõ rệt đến việc phát khoá và nhả khoá trên các đơn vị dữ liệu. Cơ chế được mô tả trong ví dụ sau :
Ví dụ : Giả sử mức cô lập là Repeatable read
[1] Begin tran
[2] Read (A)
[3] Read (B)
[4] Save tran nhan_1
[5] Read (C)
[6] Read (D)
[7] Rollback tran nhan_1
[8] Read (E)

Sau bước [6], các đơn vị dữ liệu đang bị khóa là : A, B, C và D.
Sau bước [7], các đơn vị dữ liệu đang bị khóa là : A và B. (C và D được khoá trong đoạn mã bị rollback nên sau khi rollback, các khoá này không còn nữa)
Sau bước [8], các đơn vị dữ liệu đang bị khóa là : A, B và E (Có thêm E bị khóa). Các khoá trên A, B và EC sẽ được giữ cho đến khi transaction kết thúc.

Khi nào dùng rollback một phần

Trường hợp 1 - Các thành phần có sự cùng phụ thuộc
Ta thấy rằng hầu hết các transaction đều tuân thủ tính nguyên tố. Việc sử dụng rollback một phần chỉ xảy ra trong những tình huống đặc biệt tùy vào dụng ý và sự linh động của người xây dựng transaction. Ta xét một mẫu transaction như sau :
Begin tran
1
________

Save tran stamp_1
A
________

If (lỗi 1)
Rollback tran stamp_1
Save tran stamp_2
B
________

If (lỗi 2)
Rollback tran stamp_2
Save tran stamp_3
C
________

If (lỗi 3)
Rollback tran stamp_3
________
2

Commit tran / Rollback tran
Ta thấy trong mẫu transaction tổng quát trên đây, các đoạn code A, B và C có tính độc lập với nhau. Nghĩa là một trong các đoạn này có thể bị rollback mà không liên lụy đến các đoạn còn lại. Như vậy lẽ ra chúng phải được viết trong 3 transactions khác nhau. Tuy nhiên giả sử rằng các đoạn code A, B và C lại đều phụ thuộc vào đoạn code 1 (ví dụ như đoạn code 1 kiểm tra một ràng buộc nào đó được thỏa thì mới chạy các đoạn code A, B và C). Do đó nếu viết 3 transactions riêng thì phải viết lại đoạn code 1 ba lần.
Hơn thế nữa, ta lại giả sử đoạn code 2 cùng lúc phụ thuộc vào cả 3 đoạn code A, B và C (ví dụ như nó tổng kết những gì làm được và không làm được trong A, B và C ). Lúc này, ta không còn giải pháp nào khác là phải đưa cả 5 đoạn code này vào trong 1 transaction. Đây chính là trường hợp ta cần dùng rollback một phần.
Tóm lại : Dùng rollback một phần khi
Có một nhóm thao tác mà các thao tác trong nhóm này độc lập với nhau (A, B, C) nhưng lại cùng phụ thuộc vào một hay một số thao tác khác ngoài nhóm (1).
Có một hay một số thao tác nào đó (2) phụ thuộc vào một nhóm thao tác khác mà các thao tác trong nhóm này độc lập nhau (A, B, C).
Cả hai trường hợp trên.

Nhận xétTrường hợp 2 – Kiểm tra ràng buộc tốn kém
Việc rollback một phần thường là giải pháp thay thế cho sự rẽ nhánh logic lúc kiểm tra ràng buộc toàn vẹn (nhánh 1 : Nếu thoả ràng buộc thì làm các thao tác x ; nhánh 2 : Nếu không thỏa thì làm các thao tác y). Giả sử việc kiểm tra ràng buộc toàn vẹn trong transaction đòi hỏi nhiều tài nguyên (số dòng trong bảng quá lớn, câu truy vấn phức tạp, …) thì người viết transaction có thể tránh thao tác kiểm tra này bằng cách sử dụng rollback một phần như sau :
Dùng check constraint, rule hay trigger để ngăn chặn các thay đổi trên dữ liệu làm vi phạm RBTV.
Trong transaction, xem như không có vi phạm gì xảy ra và cứ thực hiện các thao tác x. Nếu lỡ vi phạm RBTV, các check constraint sẽ phát lỗi (@@error = 547) hoặc các rule sẽ phát lỗi (@@error = 513) hoặc trigger sẽ phát lỗi (mã lỗi do người viết trigger quy định). Lúc đó trong transaction chỉ cần kiểm tra giá trị tương ứng của @@error và cho rollback các thao tác x (chỉ x mà thôi à rollback một phần) rồi thực hiện các thao tác y.
Ví dụ cụ thể
Cho lược đồ cơ sở dữ liệu quản lý đơn đặt hàng như sau :
donDatHang
Tên Kiểu dữ liệu Chú thích
maDonDatHang Varchar(10)
ngayDatHang DateTime
trangThai int 0 : chưa xuất (default) 1 : đã xuất 2 : đã hủy

chiTietDonDatHang
Tên Kiểu dữ liệu Chú thích
maDonDatHang Varchar(10)
maSanPham Varchar(10)
trangThai int 0 : chưa xuất (default) 1 : đã xuất
soLuong int

chiPhieuXuat
Tên Kiểu dữ liệu Chú thích
maPhieuXuat Varchar(10)
maSanPham Varchar(10)

phieuXuat
Tên Kiểu dữ liệu Chú thích
maPhieuXuat Varchar(10)
ngayXuat DateTime

sanPham
Tên Kiểu dữ liệu Chú thích
maSanPham Varchar(10)
tenSanPham nvarchar(100)
soTon int Check constraint : soDu > 0

Stored proc sau đây thực hiện việc xuất hàng cho một đơn đặt hàng đồng thời lập hóa đơn cho việc xuất hàng ấy. Sau khi xuất hàng xong, đơn đặt hàng sẽ có trạng thái là “đã xuất”. Với mỗi chi tiết đơn đặt hàng, nếu còn hàng thì xuất và chi tiết ấy được gán trạng thái là “đã xuất” và phát sinh một chi tiết hoá đơn tương ứng. Ngược lại nếu hết hàng rồi thì chi tiết đơn đặt hàng ấy vẫn cóvẫnb co trạng thái là “chưa xuất”. Nếu tất cả các chi tiết của một đơn đặt hàng đều không có hàng để xuất thì đơn đặt hàng đó có trạng thái là “đã hủy”.

Create proc xuLyDonDatHang @maDonDatHang varchar(10)
As
If @maDonDatHang = ‘’
Begin

Print ‘Ma don dat hang phai khac rong’

Return
End
Begin tran
Set transaction isolation level Serializable
If not exists(select * from donDatHang where maDonDatHang = @maDonDatHang and trangThai=0)
Begin

Print ‘Don dat hang khong ton tai hoac da duoc xu ly roi’

Rollback tran

Return
End
Insert into phieuXuat values(@maDonDatHang, Getdate())
If (@@error <> 0)
Begin

Print ‘Khong the them phieu xuat’

Rollback tran

return
End
Declare cur_chiTiet cursor for (select maSanPham, soLuong from chiTietDonDatHang where maDondatHang = @maDonDatHang)
Open cur_chiTiet
Declare @maSanPham vachar(10)
Declare @soLuong int
Declare @soChiTietXuat int
Set @soChiTietXuat = 0
Fetch next from cur_chiTiet into @maSanPham, @soLuong
While (@@ferch_status = 0)
Begin

Save tran @maSanPham

Insert into chiTietPhieuXuat (@maDonDatHang,@maSanPham)

If (@@error <> 0)

Begin

Print ‘Khong the them phieu xuat’

Rollback tran

return

End
Update chiTietDonDatHang set trangThai = 1 where maDonDatHang = @maDonDatHang and maSanPham = @maSanPham

If (@@error <> 0)

Begin

Print ‘Khong cap nhat duoc trang thai chi tiet don dat hang’

Rollback tran

return

End

Update sanPham set soTon = soTon - @soLuong where maSanPham = @maSanPham

If (@@error = 547)

Rollback tran @maSanPham

ElseIf (@@error <> 0)

Begin

Print ‘Khong the them phieu xuat’

Rollback tran

return

End

Else

Set @soChiTietXuat = @soChiTietXuat + 1

Fetch next from cur_chiTiet into @maSanPham, @soLuong
End
If (@soChiTietXuat = 0)
Begin

Print ‘Toan bo don dat hang bi huy’

Update donDatHang set trangThai = 2 where maDondatHang = @maDondatHang

Rollback tran

Return
End
Update donDatHang set trangThai = 1 where maDondatHang = @maDondatHang
If (@@error <> 0)
Begin

Print ‘Khong cap nhat duoc trang thai don dat hang’

Rollback tran

return
End
Commit tran
Go

1 comment:

  1. MINH DANG CAN CAU LENH INSERT DULIEU PROC
    TU 3 BANG SAU
    HOADON(MAHD,MSNV,MASKH,NGAYHD,TONGTIEN)
    CHITIETHD(MAHD,MAMH,SOLUONG,THANHTIEN)
    KHACHHANG(MAKH,TENKH,DIACHI,DIENTHOAI)
    .MONG BAN GIUP MINH

    ReplyDelete

Nội dung nhận xét của bạn đang được kiểm duyệt.
Vui lòng chờ ....