Một trong những vấn đề phổ biến nhất xảy ra khi chạy các giao dịch đồng thời là vấn đề Dirty Read. Đọc bẩn xảy ra khi một giao dịch được phép đọc dữ liệu đang được sửa đổi bởi một giao dịch khác đang chạy đồng thời nhưng chưa được cam kết.
Nếu giao dịch sửa đổi dữ liệu tự cam kết thì vấn đề đọc bẩn sẽ không xảy ra. Tuy nhiên, nếu giao dịch sửa đổi dữ liệu được khôi phục sau khi giao dịch khác đã đọc dữ liệu, thì giao dịch sau có dữ liệu bẩn không thực sự tồn tại.
Như mọi khi, hãy đảm bảo rằng bạn đã được sao lưu tốt trước khi thử nghiệm với một mã mới. Xem bài viết này về cách sao lưu cơ sở dữ liệu MS SQL nếu bạn không chắc chắn.
Hãy hiểu điều này với sự trợ giúp của một ví dụ. Giả sử chúng ta có một bảng có tên là "Sản phẩm" lưu trữ id, tên và ItemsinStock cho sản phẩm.
Bảng trông như sau:
[id bảng =20 /]
Giả sử bạn có một hệ thống trực tuyến nơi người dùng có thể mua sản phẩm và xem sản phẩm cùng một lúc. Hãy xem hình sau.
Hãy xem xét một tình huống trong đó người dùng cố gắng mua một sản phẩm. Giao dịch 1 sẽ thực hiện nhiệm vụ mua hàng cho người dùng. Bước đầu tiên trong giao dịch sẽ là cập nhật ItemsinStock.
Trước khi giao dịch, có 12 mặt hàng trong kho; giao dịch sẽ cập nhật điều này thành 11. Giao dịch bây giờ sẽ giao tiếp với một cổng thanh toán bên ngoài.
Nếu tại thời điểm này, một giao dịch khác, giả sử Giao dịch 2, đọc ItemsInStock cho máy tính xách tay, nó sẽ đọc 11. Tuy nhiên, nếu sau đó, người dùng thực hiện Giao dịch 1 không có đủ tiền trong tài khoản của mình, Giao dịch 1 sẽ được thực hiện trở lại và giá trị cho cột ItemsInStock sẽ hoàn nguyên về 12.
Tuy nhiên, Giao dịch 2 có 11 là giá trị cho cột ItemsInStock. Đây là dữ liệu bẩn và sự cố được gọi là sự cố đọc bẩn.
Ví dụ làm việc về vấn đề đọc bẩn
Hãy cùng chúng tôi xem xét sự cố đọc bẩn đang diễn ra trong SQL Server. Như mọi khi, trước tiên, hãy tạo bảng của chúng tôi và thêm một số dữ liệu giả vào bảng. Thực thi tập lệnh sau trên máy chủ cơ sở dữ liệu của bạn.
CREATE DATABASE pos;
USE pos;
CREATE TABLE products
(
Id INT PRIMARY KEY,
Name VARCHAR(50) NOT NULL,
ItemsinStock INT NOT NULL
)
INSERT into products
VALUES
(1, 'Laptop', 12),
(2, 'iPhone', 15),
(3, 'Tablets', 10)
Bây giờ, mở hai phiên bản studio quản lý máy chủ SQL cạnh nhau. Chúng tôi sẽ chạy một giao dịch trong mỗi trường hợp này.
Thêm tập lệnh sau vào phiên bản đầu tiên của SSMS.
USE pos;
SELECT * FROM products
-- Transaction 1
BEGIN Tran
UPDATE products set ItemsInStock = 11
WHERE Id = 1
-- Billing the customer
WaitFor Delay '00:00:10'
Rollback Transaction
Trong tập lệnh trên, chúng tôi bắt đầu một giao dịch mới cập nhật giá trị cho cột "ItemsInStock" của bảng sản phẩm trong đó Id là 1. Sau đó, chúng tôi mô phỏng độ trễ để thanh toán cho khách hàng bằng cách sử dụng chức năng "WaitFor" và "Delay". Độ trễ 10 giây đã được đặt trong tập lệnh. Sau đó, chúng tôi chỉ cần khôi phục giao dịch.
Trong trường hợp thứ hai của SSMS, chúng tôi chỉ cần thêm câu lệnh SELECT sau.
USE pos;
-- Transaction 2
SELECT * FROM products
WHERE Id = 1
Bây giờ, trước tiên hãy chạy giao dịch đầu tiên, tức là thực thi tập lệnh trong phiên bản đầu tiên của SSMS, sau đó thực thi ngay tập lệnh trong phiên bản thứ hai của SSMS.
Bạn sẽ thấy rằng cả hai giao dịch sẽ tiếp tục thực hiện trong 10 giây và sau đó, bạn sẽ thấy rằng giá trị cho cột ‘ItemsInStock’ cho bản ghi có Id 1 vẫn là 12 như được hiển thị trong giao dịch thứ hai. Mặc dù giao dịch đầu tiên đã cập nhật nó thành 11, đợi trong 10 giây và sau đó đưa nó trở lại 12, giá trị được hiển thị bởi giao dịch thứ hai là 12 thay vì 11.
Điều thực sự đã xảy ra là khi chúng tôi chạy giao dịch đầu tiên, nó đã cập nhật giá trị cho cột ‘ItemsinStock’. Sau đó, nó đợi 10 giây và sau đó quay trở lại giao dịch.
Mặc dù chúng tôi bắt đầu giao dịch thứ hai ngay sau giao dịch đầu tiên, nhưng phải đợi giao dịch đầu tiên hoàn tất. Đó là lý do tại sao giao dịch thứ hai cũng đợi trong 10 giây và tại sao giao dịch thứ hai được thực hiện ngay sau khi giao dịch đầu tiên hoàn thành việc thực hiện.
Đọc mức cô lập đã cam kết
Tại sao giao dịch 2 phải đợi giao dịch 1 hoàn thành trước khi thực hiện?
Câu trả lời là mức độ cách ly mặc định giữa các giao dịch là "đọc cam kết". Mức cách ly được cam kết đọc đảm bảo rằng dữ liệu chỉ có thể được đọc bởi một giao dịch nếu nó ở trạng thái đã cam kết.
Trong ví dụ của chúng tôi, giao dịch 1 đã cập nhật dữ liệu nhưng nó không cam kết cho đến khi được khôi phục lại. Đây là lý do tại sao giao dịch 2 phải đợi giao dịch 1 xác nhận dữ liệu hoặc khôi phục giao dịch trước khi nó có thể đọc dữ liệu.
Bây giờ, trong các tình huống thực tế, chúng tôi thường có nhiều giao dịch diễn ra trên một cơ sở dữ liệu cùng một lúc và chúng tôi không muốn mọi giao dịch phải chờ đến lượt. Điều này có thể làm cho cơ sở dữ liệu rất chậm. Hãy tưởng tượng mua thứ gì đó trực tuyến từ một trang web lớn chỉ có thể xử lý một giao dịch tại một thời điểm!
Đọc dữ liệu không được gửi
Câu trả lời cho vấn đề này là cho phép các giao dịch của bạn hoạt động với dữ liệu không được cam kết.
Để đọc dữ liệu chưa được cam kết, chỉ cần đặt mức cô lập của giao dịch thành “đọc không được cam kết”. Cập nhật giao dịch 2 bằng cách thêm mức cô lập theo tập lệnh bên dưới.
USE pos;
-- Transaction 2
set transaction isolation level read uncommitted
SELECT * FROM products
WHERE Id = 1
Bây giờ nếu bạn chạy giao dịch 1 và sau đó ngay lập tức chạy giao dịch 2, bạn sẽ thấy rằng giao dịch 2 sẽ không đợi giao dịch 1 để cam kết dữ liệu. Giao dịch 2 sẽ ngay lập tức đọc dữ liệu bẩn. Điều này được thể hiện trong hình sau:
Đây là phiên bản bên trái đang chạy giao dịch 1 và phiên bản bên phải đang chạy giao dịch 2.
Chúng tôi chạy giao dịch 1 trước tiên sẽ cập nhật giá trị của “ItemsinStock” cho id từ 1 đến 11 từ 12 và sau đó đợi 10 giây trước khi được khôi phục.
Trong khi đó, giao dịch w đọc dữ liệu bẩn là 11, như được hiển thị trong cửa sổ kết quả ở bên phải. Bởi vì giao dịch 1 được lùi lại, đây không phải là giá trị thực tế trong bảng. Giá trị thực là 12. Hãy thử thực hiện lại giao dịch 2 và bạn sẽ thấy rằng lần này nó truy xuất 12.
Đọc không cam kết là mức cách ly duy nhất có vấn đề đọc bẩn. Mức cô lập này ít hạn chế nhất trong tất cả các mức cô lập và cho phép đọc dữ liệu không được cam kết.
Rõ ràng, có những ưu và nhược điểm khi sử dụng Đọc không được chấp nhận, nó phụ thuộc vào ứng dụng mà cơ sở dữ liệu của bạn được sử dụng. Rõ ràng, sẽ là một ý tưởng rất tồi nếu sử dụng nó cho cơ sở dữ liệu đằng sau hệ thống ATM và các hệ thống rất an toàn khác. Tuy nhiên, đối với các ứng dụng mà tốc độ là rất quan trọng (chạy các cửa hàng thương mại điện tử lớn) thì việc sử dụng Read Uncommiss sẽ có ý nghĩa hơn.