Database
 sql >> Baza danych >  >> RDS >> Database

Problem utraconej aktualizacji w jednoczesnych transakcjach

Problem utraconej aktualizacji występuje, gdy 2 współbieżne transakcje próbują odczytać i zaktualizować te same dane. Zrozummy to na przykładzie.

Załóżmy, że mamy tabelę o nazwie „Produkt”, która przechowuje identyfikator, nazwę i ItemsinStock dla produktu.

Jest używany jako część systemu online, który wyświetla liczbę produktów w magazynie dla danego produktu, dlatego musi być aktualizowany za każdym razem, gdy dokonywana jest sprzedaż tego produktu.

Tabela wygląda tak:

Identyfikator

Nazwa

Przedmioty w magazynie

1

Laptopy

12

Rozważmy teraz scenariusz, w którym pojawia się użytkownik i rozpoczyna proces zakupu laptopa. To zainicjuje transakcję. Nazwijmy tę transakcję transakcją 1.

W tym samym czasie inny użytkownik loguje się do systemu i inicjuje transakcję, nazwijmy ją 2. Spójrz na poniższy rysunek.

Transakcja 1 odczytuje pozycje w magazynie dla laptopów, czyli 12. Nieco później transakcja 2 odczytuje wartość ItemsinStock dla laptopów, która w tym momencie nadal będzie wynosić 12. Transakcja 2 następnie sprzedaje trzy laptopy, na krótko przed transakcją 1 sprzedaje 2 przedmioty.

Transakcja 2 najpierw zakończy realizację i zaktualizuje ItemsinStock do 9, ponieważ sprzedano trzy z 12 laptopów. Transakcja 1 zobowiązuje się. Ponieważ transakcja 1 sprzedała dwa przedmioty, aktualizuje ItemsinStock do 10.

To jest niepoprawne, prawidłowa liczba to 12-3-2 =7

Przykład roboczy problemu z utraconą aktualizacją

Przyjrzyjmy się problemowi utraconej aktualizacji w działaniu w SQL Server. Jak zawsze, najpierw utworzymy tabelę i dodamy do niej kilka fikcyjnych danych.

Jak zawsze, upewnij się, że masz odpowiednią kopię zapasową, zanim zaczniesz grać z nowym kodem. Jeśli nie masz pewności, zapoznaj się z tym artykułem o kopii zapasowej SQL Server.

Wykonaj następujący skrypt na serwerze bazy danych.

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

Teraz otwórz obok siebie dwie instancje SQL Server Management Studio. W każdym z tych przypadków przeprowadzimy jedną transakcję.

Dodaj następujący skrypt do pierwszej instancji 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>

To jest skrypt dla transakcji 1. Tutaj rozpoczynamy transakcję i deklarujemy zmienną typu integer „@ItemsInStock”. Wartość tej zmiennej jest ustawiona na wartość kolumny ItemsinStock dla rekordu o identyfikatorze 1 z tabeli products. Następnie dodawane jest opóźnienie 12 sekund, tak aby transakcja 2 mogła zakończyć swoją realizację przed transakcją 1. Po opóźnieniu wartość zmiennej @ItemsInStock jest zmniejszana o 2, co oznacza sprzedaż 2 produktów.

Na koniec wartość kolumny ItemsinStock dla rekordu o identyfikatorze 1 jest aktualizowana wartością zmiennej @ItemsInStock. Następnie wyświetlamy na ekranie wartość zmiennej @ItemsInStock i zatwierdzamy transakcję.

W drugiej instancji SSMS dodajemy skrypt dla transakcji 2, który wygląda następująco:

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

Skrypt transakcji 2 jest podobny do transakcji 1. Jednak tutaj, w transakcji 2, opóźnienie wynosi tylko trzy sekundy, a spadek wartości zmiennej @ItemsInStock wynosi trzy, ponieważ jest to sprzedaż trzech przedmiotów.

Teraz uruchom transakcję 1, a następnie transakcję 2. Zobaczysz, że transakcja 2 kończy swoją realizację jako pierwsza. A wartość wydrukowana dla zmiennej @ItemsInStock będzie wynosić 9. Po pewnym czasie transakcja 1 również zakończy swoje wykonanie, a wartość wydrukowana dla jej zmiennej @ItemsInStock będzie wynosić 10.

Obie te wartości są nieprawidłowe, rzeczywista wartość kolumny ItemsInStock dla produktu o identyfikatorze 1 powinna wynosić 7.

UWAGA:

Należy tutaj zauważyć, że problem z utraconą aktualizacją występuje tylko w przypadku odczytu zatwierdzonego i odczytu niezatwierdzonych poziomów izolacji transakcji. Przy wszystkich innych poziomach izolacji transakcji ten problem nie występuje.

Odczytaj poziom izolacji transakcji powtarzalnych

Zaktualizujmy poziom izolacji dla obu transakcji, aby odczytać powtarzalne i zobaczmy, czy wystąpił problem z utraconą aktualizacją. Ale wcześniej wykonaj następującą instrukcję, aby zaktualizować wartość ItemsInStock z powrotem do 12.

Update products SET ItemsinStock = 12

Skrypt transakcji 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>

Skrypt transakcji 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>

Tutaj w obu transakcjach ustawiliśmy poziom izolacji na odczyt powtarzalny.

Teraz uruchom transakcję 1, a następnie natychmiast uruchom transakcję 2. W przeciwieństwie do poprzedniego przypadku, transakcja 2 będzie musiała poczekać, aż transakcja 1 zostanie zatwierdzona. Następnie pojawia się następujący błąd dla transakcji 2:

Msg 1205, poziom 13, stan 51, wiersz 15

Transakcja (identyfikator procesu 55) została zablokowana w zasobach blokady przez inny proces i została wybrana jako ofiara zakleszczenia. Ponownie uruchom transakcję.

Ten błąd występuje, ponieważ odczyt powtarzalny blokuje zasób, który jest odczytywany lub aktualizowany przez transakcję 1 i tworzy zakleszczenie w innej transakcji, która próbuje uzyskać dostęp do tego samego zasobu.

Błąd mówi, że transakcja 2 ma zakleszczenie w zasobie z innym procesem i że ta transakcja została zablokowana przez zakleszczenie. Oznacza to, że druga transakcja uzyskała dostęp do zasobu, podczas gdy ta transakcja została zablokowana i nie otrzymała dostępu do zasobu.

Mówi również, aby ponownie uruchomić transakcję, ponieważ zasób jest teraz wolny. Teraz, jeśli ponownie uruchomisz transakcję 2, zobaczysz poprawną wartość pozycji w magazynie, tj. 7. Dzieje się tak, ponieważ transakcja 1 zmniejszyła już wartość IteminStock o 2, transakcja 2 dodatkowo zmniejsza ją o 3, więc 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. Jak wybrać pierwszy wiersz w każdej GRUPIE WEDŁUG GRUP

  2. Minimalizowanie wpływu poszerzenia kolumny TOŻSAMOŚĆ – część 3

  3. ETL kontra ELT:zakładamy, ty sędzio

  4. FrankenQueries:kiedy SQL i NoSQL zderzają się

  5. GROUP BY vs ORDER BY