Jak zapytać i zwiększyć wartość (licznik) w sposób bezpieczny dla wątków? (unikaj warunków wyścigu)

10

W tabeli, gdzie każdy wiersz posiada licznik (tylko wartość całkowita), muszę dostać aktualną wartość i zwiększyć ją w tym samym czasie .

Skutecznie chcę to zrobić:

SELECT counter FROM table WHERE id=123
UPDATE table SET counter=counter+1 WHERE id=123

Ale robienie tego, ponieważ dwa zapytania nie są oczywiście bezpieczne dla wątków: wiele procesów wykonujących tę samą czynność (w tym samym wierszu) może otrzymać tę samą wartość licznika. Muszę im wszystkim być wyjątkowa, więc każdy proces będzie uzyskać rzeczywistą wartość prądu i zwiększać ją o jeden.

Mogę wymyślić konstrukcję, w której wdrażam ręczną blokadę na rząd, ale zastanawiam się, czy istnieje łatwiejszy sposób na zrobienie tego?

RocketNuts
źródło
może korzystasz z transakcji?
ypercubeᵀᴹ

Odpowiedzi:

15

Instrukcje aktualizacji działają doskonale bez wcześniejszego wyboru! Ponieważ pojedyncze instrukcje są z definicji bezpieczne, nawet dwa zapytania UPDATE wykonywane w tym samym czasie spowodują dwukrotne zwiększenie wiersza.

Jeśli naprawdę chcesz wybrać wartość dla skryptu PHP, zrób coś z tym, a później chcesz zaktualizować tę dokładną wartość licznika, możesz wykonać następujące czynności:

BEGIN;
SELECT `counter` FROM `table` WHERE `id` = 123 FOR UPDATE;
UPDATE `table` SET `counter` = `counter`+1 WHERE `id` = 123;
COMMIT;

To rozpoczyna nową transakcję, a następnie wybiera wiersze, które chcesz zaktualizować, i blokuje je wyłącznie. Następnie możesz je bezpiecznie aktualizować, nie martwiąc się o to, że inni klienci zmienią ich zawartość lub nawet uzyskają dostęp do zablokowanych wierszy. Wreszcie musisz zatwierdzić zmiany.

Powinieneś także przeczytać coś o poziomach izolacji . Prawdopodobnie nie chcesz wartości takiej READ UNCOMMITTEDjak poziom izolacji. Wszystko inne powinno być w porządku w tym przypadku użycia.

GhostGambler
źródło
Czytałem w innych miejscach, że UPDATE table SET counter = counter + 1jest wystarczająco atomowy? Czy nadal potrzebujesz otaczających go wyciągów z transakcji?
CMCDragonkai
@CMCDragonkai Samo twoje zapytanie jest atomowe, ale jeśli wybierzesz wartość wcześniej i nie używałeś FOR UPDATEtransakcji oraz transakcji, wybrana wartość może być inna niż ta, która została użyta w zapytaniu aktualizacyjnym. Moja kombinacja zapytań blokuje wiersz natychmiast po wybraniu wartości i dlatego zapewnia, że ​​ta dokładna wartość licznika zostanie użyta w zapytaniu o aktualizację.
GhostGambler
Ok, ale jest to konieczne tylko wtedy, gdy wykonuję jakąś pracę inną niż zwiększenie, prawda? W tej chwili pojedyncze zapytanie o aktualizację atomową jest wystarczająco dobre, jeśli to wszystko, co chcę zrobić?
CMCDragonkai
1
@CMCDragonkai Jeśli nie wykonasz innego zapytania, które dotyka kolumny, jesteś dobry.
GhostGambler