Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Sự cố cập nhật bị mất trong các giao dịch đồng thời

Sự cố cập nhật bị mất xảy ra khi 2 giao dịch đồng thời cố gắng đọc và cập nhật cùng một dữ liệu. 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 “Sản phẩm” lưu trữ id, tên và ItemsinStock cho một sản phẩm.

Nó được sử dụng như một phần của hệ thống trực tuyến hiển thị số lượng mặt hàng trong kho cho một sản phẩm cụ thể và do đó cần được cập nhật mỗi khi bán sản phẩm đó.

Bảng trông như sau:

Id

Tên

ItemsinStock

1

Máy tính xách tay

12

Bây giờ hãy xem xét một tình huống trong đó người dùng đến và bắt đầu quá trình mua máy tính xách tay. Điều này sẽ bắt đầu một giao dịch. Hãy gọi đây là giao dịch, giao dịch 1.

Đồng thời, một người dùng khác đăng nhập vào hệ thống và bắt đầu một giao dịch, hãy gọi giao dịch này là 2. Hãy xem hình sau.

Giao dịch 1 đọc các mục trong kho đối với máy tính xách tay là 12. Một chút sau đó, giao dịch 2 đọc giá trị của ItemsinStock đối với máy tính xách tay vẫn sẽ là 12 tại thời điểm này. Giao dịch 2 sau đó bán ba máy tính xách tay, ngay trước giao dịch 1 bán 2 mặt hàng.

Giao dịch 2 sau đó sẽ hoàn thành việc thực hiện trước và cập nhật ItemsinStock lên 9 vì nó đã bán được ba trong số 12 máy tính xách tay. Giao dịch 1 tự cam kết. Vì giao dịch 1 đã bán hai mặt hàng nên nó cập nhật ItemsinStock thành 10.

Điều này không chính xác, hình đúng là 12-3-2 =7

Ví dụ làm việc về sự cố cập nhật bị mất

Hãy cùng chúng tôi xem xét sự cố cập nhật bị mất đang hoạt động trong SQL Server. Như mọi khi, trước tiên, chúng ta sẽ tạo một bảng và thêm một số dữ liệu giả vào đó.

Như mọi khi, hãy đảm bảo rằng bạn đã được sao lưu đúng cách trước khi chơi với mã mới. Nếu bạn không chắc chắn, hãy xem bài viết này về sao lưu SQL Server.

Thực thi tập lệnh sau trên máy chủ cơ sở dữ liệu của bạn.

<span style="font-size: 14px;">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, 'Iphon', 15),
(3, 'Tablets', 10)</span>

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.

<span style="font-size: 14px;">USE pos;

-- Transaction 1

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Đây là tập lệnh cho giao dịch 1. Tại đây chúng ta bắt đầu giao dịch và khai báo một biến kiểu số nguyên “@ItemsInStock”. Giá trị của biến này được đặt thành giá trị của cột ItemsinStock cho bản ghi có Id 1 từ bảng sản phẩm. Sau đó, thời gian trễ 12 giây được thêm vào để giao dịch 2 có thể hoàn tất việc thực hiện trước giao dịch 1. Sau thời gian trì hoãn, giá trị của biến @ItemsInStock giảm đi 2 biểu thị việc bán 2 sản phẩm.

Cuối cùng, giá trị cho cột ItemsinStock cho bản ghi có Id 1 được cập nhật với giá trị của biến @ItemsInStock. Sau đó, chúng tôi in giá trị của biến @ItemsInStock trên màn hình và thực hiện giao dịch.

Trong trường hợp thứ hai của SSMS, chúng tôi thêm tập lệnh cho giao dịch 2 như sau:

<span style="font-size: 14px;">USE pos;

-- Transaction 2

BEGIN TRAN

DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Tập lệnh cho giao dịch 2 tương tự như giao dịch 1. Tuy nhiên, ở đây trong giao dịch 2, độ trễ chỉ là ba giây và sự giảm giá trị cho biến @ItemsInStock là ba, vì nó là một đợt bán ba mặt hàng.

Bây giờ, hãy chạy giao dịch 1 và sau đó là giao dịch 2. Trước tiên, bạn sẽ thấy giao dịch 2 hoàn tất việc thực hiện. Và giá trị được in cho biến @ItemsInStock sẽ là 9. Sau một thời gian, giao dịch 1 cũng sẽ hoàn thành việc thực thi và giá trị được in cho biến @ItemsInStock của nó sẽ là 10.

Cả hai giá trị này đều sai, giá trị thực tế cho cột ItemsInStock cho sản phẩm có Id 1 phải là 7.

LƯU Ý:

Điều quan trọng cần lưu ý ở đây là sự cố cập nhật bị mất chỉ xảy ra với các mức cách ly giao dịch được cam kết đọc và đọc không cam kết. Với tất cả các mức cách ly giao dịch khác, sự cố này không xảy ra.

Đọc mức độ cách ly giao dịch lặp lại

Hãy cập nhật mức độ cách ly để cả hai giao dịch có thể đọc lặp lại và xem liệu sự cố cập nhật bị mất có xảy ra hay không. Nhưng trước đó, hãy thực hiện câu lệnh sau để cập nhật giá trị cho ItemsInStock trở lại 12.

Update products SET ItemsinStock = 12

Tập lệnh cho Giao dịch 1

<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 1

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:12'
SET @ItemsInStock = @ItemsInStock - 2

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Tập lệnh cho Giao dịch 2

<span style="font-size: 14px;">USE pos;

SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
-- Transaction 2

BEGIN TRAN
DECLARE @ItemsInStock INT

SELECT @ItemsInStock = ItemsInStock
FROM products WHERE Id = 1

WaitFor Delay '00:00:3'
SET @ItemsInStock = @ItemsInStock - 3

UPDATE products SET ItemsinStock = @ItemsInStock
WHERE Id = 1

Print @ItemsInStock
Commit Transaction</span>

Ở đây trong cả hai giao dịch, chúng tôi đã đặt mức cô lập thành đọc có thể lặp lại.

Bây giờ chạy giao dịch 1 và sau đó ngay lập tức chạy giao dịch 2. Không giống như trường hợp trước, giao dịch 2 sẽ phải đợi giao dịch 1 tự cam kết. Sau đó, lỗi sau xảy ra cho giao dịch 2:

Bản tin 1205, Mức 13, Trạng thái 51, Dòng 15

Giao dịch (Process ID 55) đã bị khóa trên tài nguyên khóa với một quy trình khác và đã được chọn làm nạn nhân của khóa chết. Chạy lại giao dịch.

Lỗi này xảy ra vì đọc lặp lại khóa tài nguyên đang được đọc hoặc cập nhật bởi giao dịch 1 và nó tạo ra một bế tắc trên giao dịch khác cố gắng truy cập vào cùng một tài nguyên.

Lỗi cho biết rằng giao dịch 2 có bế tắc trên một tài nguyên với một quy trình khác và giao dịch này đã bị khóa bởi bế tắc. Điều này có nghĩa là giao dịch khác đã được cấp quyền truy cập vào tài nguyên trong khi giao dịch này bị chặn và không được cấp quyền truy cập vào tài nguyên.

Nó cũng cho biết chạy lại giao dịch vì tài nguyên hiện đang miễn phí. Bây giờ, nếu bạn chạy lại giao dịch 2, bạn sẽ thấy giá trị chính xác của các mặt hàng trong kho, tức là 7. Điều này là do giao dịch 1 đã giảm giá trị IteminStock đi 2, giao dịch 2 tiếp tục giảm giá trị này thêm 3, do đó 12 - (2+ 3) =7.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Kiểm tra các tuyên bố DML cho OLTP trong bộ nhớ

  2. 115 câu hỏi phỏng vấn SQL hàng đầu bạn phải chuẩn bị vào năm 2022

  3. Toán tử SQL EXISTS cho người mới bắt đầu

  4. Cách lấy Ngày hôm qua trong T-SQL

  5. Sử dụng AT TIME ZONE để sửa một báo cáo cũ