To, czego chcesz, to WYBIERZ ... DO AKTUALIZACJI w kontekście transakcji. WYBIERZ NA AKTUALIZACJĘ blokuje wyłączne zaznaczenie wybranych wierszy, tak jakbyś wykonywał aktualizację. Działa również domyślnie na poziomie izolacji PRZECZYTAJ ZOBOWIĄZANIE, niezależnie od tego, na jaki poziom izolacji jest jawnie ustawiony. Pamiętaj tylko, że WYBIERZ ... DO AKTUALIZACJI jest bardzo zły dla współbieżności i powinien być używany tylko wtedy, gdy jest to absolutnie konieczne. Ma również tendencję do namnażania się w bazie kodu, gdy ludzie wycinają i wklejają.
Oto przykładowa sesja z bazy danych Sakila, która demonstruje niektóre zachowania zapytań FOR UPDATE.
Po pierwsze, abyśmy byli krystalicznie czysti, ustaw poziom izolacji transakcji na POWTARZALNE CZYTANIE. Jest to zwykle niepotrzebne, ponieważ jest to domyślny poziom izolacji dla InnoDB:
session1> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
session1> BEGIN;
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
W drugiej sesji zaktualizuj ten wiersz. Linda wyszła za mąż i zmieniła imię:
session2> UPDATE customer SET last_name = 'BROWN' WHERE customer_id = 3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Z powrotem w sesji 1, ponieważ byliśmy w REPEATABLE READ, Linda nadal jest LINDA WILLIAMS:
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | WILLIAMS |
+------------+-----------+
1 row in set (0.00 sec)
Ale teraz chcemy wyłącznego dostępu do tego wiersza, dlatego w wierszu wywołujemy FOR UPDATE. Zauważ, że otrzymujemy teraz najnowszą wersję wiersza, która została zaktualizowana w sesji 2 poza tą transakcją. To NIE POWTARZANE CZYTANIE, to CZYTANIE ZOBOWIĄZANE
session1> SELECT first_name, last_name FROM customer WHERE customer_id = 3 FOR UPDATE;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| LINDA | BROWN |
+------------+-----------+
1 row in set (0.00 sec)
Przetestujmy blokadę ustawioną w session1. Pamiętaj, że session2 nie może zaktualizować wiersza.
session2> UPDATE customer SET last_name = 'SMITH' WHERE customer_id = 3;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
Ale nadal możemy z niego wybierać
session2> SELECT c.customer_id, c.first_name, c.last_name, a.address_id, a.address FROM customer c JOIN address a USING (address_id) WHERE c.customer_id = 3;
+-------------+------------+-----------+------------+-------------------+
| customer_id | first_name | last_name | address_id | address |
+-------------+------------+-----------+------------+-------------------+
| 3 | LINDA | BROWN | 7 | 692 Joliet Street |
+-------------+------------+-----------+------------+-------------------+
1 row in set (0.00 sec)
Nadal możemy aktualizować tabelę podrzędną za pomocą relacji klucza obcego
session2> UPDATE address SET address = '5 Main Street' WHERE address_id = 7;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
session1> COMMIT;
Kolejnym efektem ubocznym jest znaczne zwiększenie prawdopodobieństwa spowodowania impasu.
W twoim konkretnym przypadku prawdopodobnie chcesz:
BEGIN;
SELECT id FROM `items` WHERE `status`='pending' LIMIT 1 FOR UPDATE;
-- do some other stuff
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `id`=<selected id>;
COMMIT;
Jeśli fragment „zrób coś innego” jest niepotrzebny i nie musisz tak naprawdę przechowywać informacji o wierszu, wybierz WYBIERZ AKTUALIZACJĘ jest niepotrzebny i marnotrawny, a zamiast tego możesz po prostu uruchomić aktualizację:
UPDATE `items` SET `status`='working', `updated`=NOW() WHERE `status`='pending' LIMIT 1;
Mam nadzieję, że to ma jakiś sens.
items
WHEREstatus
=„ w toku ”LIMIT 1 DO AKTUALIZACJI;” i oboje widzą ten sam rząd, a następnie jeden zablokuje drugi. Miałem nadzieję, że jakoś uda się ominąć zamknięty rząd i przejść do następnego, który był w toku.Jeśli używasz silnika pamięci InnoDB, używa on blokowania na poziomie wiersza. W połączeniu z wersją wielu wersji powoduje to dobrą współbieżność zapytań, ponieważ dana tabela może być odczytywana i modyfikowana przez różnych klientów jednocześnie. Właściwości współbieżności na poziomie wiersza są następujące:
Różni klienci mogą jednocześnie czytać te same wiersze.
Różni klienci mogą modyfikować różne wiersze jednocześnie.
Różni klienci nie mogą modyfikować tego samego wiersza w tym samym czasie. Jeśli jedna transakcja modyfikuje wiersz, inne transakcje nie mogą modyfikować tego samego wiersza, dopóki pierwsza transakcja nie zostanie zakończona. Inne transakcje również nie mogą odczytać zmodyfikowanego wiersza, chyba że używają poziomu izolacji ODCZYTAJ NIEZGODNOŚĆ. Oznacza to, że zobaczą oryginalny niezmodyfikowany wiersz.
Zasadniczo nie musisz określać jawnego blokowania InnoDB obsługuje to samo, chociaż w niektórych sytuacjach może być konieczne podanie jawnych szczegółów blokowania jawnego blokowania podano poniżej:
Poniższa lista opisuje dostępne typy blokad i ich efekty:
CZYTAĆ
Blokuje stolik do czytania. Blokada READ blokuje tabelę dla zapytań odczytu, takich jak SELECT, które pobierają dane z tabeli. Nie zezwala na operacje zapisu, takie jak INSERT, DELETE lub UPDATE, które modyfikują tabelę, nawet przez klienta posiadającego blokadę. Gdy tabela jest zablokowana do odczytu, inni klienci mogą czytać z tabeli w tym samym czasie, ale żaden klient nie może do niej pisać. Klient, który chce pisać do tabeli z blokadą odczytu, musi poczekać, aż wszyscy klienci czytający z niej zakończą i zwolnią blokady.
PISAĆ
Blokuje stół do pisania. Zamek WRITE to zamek wyłączny. Można go zdobyć tylko wtedy, gdy nie jest używany stół. Po uzyskaniu tylko klient posiadający blokadę zapisu może czytać lub zapisywać w tabeli. Inni klienci nie mogą ani czytać, ani pisać. Żaden inny klient nie może zablokować tabeli ani czytać, ani pisać.
PRZECZYTAJ LOKALNIE
Blokuje tabelę do odczytu, ale umożliwia jednoczesne wstawianie. Jednoczesna wstawka stanowi wyjątek od zasady „czytników bloków zapisujących”. Dotyczy tylko tabel MyISAM. Jeśli w tabeli MyISAM nie ma żadnych otworów pośrodku wynikających z usuniętych lub zaktualizowanych rekordów, wstawianie zawsze odbywa się na końcu tabeli. W takim przypadku klient, który czyta z tabeli, może zablokować ją blokadą CZYTAJ LOKALNIE, aby umożliwić innym klientom wstawianie do tabeli, podczas gdy klient trzymający blokadę odczytu czyta z niej. Jeśli w tabeli MyISAM są dziury, możesz je usunąć za pomocą OPTYMALIZACJI TABELI w celu defragmentacji tabeli.
źródło
Inną alternatywą byłoby dodanie kolumny, która przechowywała czas ostatniej udanej blokady, a następnie wszystko, co chciało zablokować wiersz, musiałoby poczekać, aż zostanie wyczyszczone lub upłynie 5 minut (lub cokolwiek innego).
Coś jak...
lastlock jest int, ponieważ przechowuje uniksowy znacznik czasu jako łatwiejszy (a może i szybszy) do porównania.
// Przepraszam za semantykę, nie sprawdziłem, czy uruchamiają się ostro, ale jeśli nie, powinny być wystarczająco blisko.
Następnie sprawdź, ile wierszy zostało zaktualizowanych, ponieważ wiersze nie mogą być aktualizowane przez dwa procesy jednocześnie, jeśli zaktualizowałeś wiersz, dostałeś blokadę. Zakładając, że używasz PHP, użyłbyś mysql_affected_rows (), jeśli zwrot z tego wynosił 1, pomyślnie go zablokowałeś.
Następnie możesz albo zaktualizować ostatnią blokadę do zera po zrobieniu tego, co musisz zrobić, albo być leniwym i czekać 5 minut, aż kolejna próba blokady się powiedzie.
EDYCJA: Może być konieczne trochę pracy, aby sprawdzić, czy działa zgodnie z oczekiwaniami wokół zmian czasu letniego, ponieważ zegary cofną się o godzinę, być może unieważniając czek. Musisz upewnić się, że uniksowe znaczniki czasu były w UTC - które i tak mogą być.
źródło
Alternatywnie, możesz pofragmentować pola rekordu, aby umożliwić równoległe zapisywanie i ominąć blokowanie wiersza (styl podzielonych par json). Więc jeśli jedno pole w złożonym odczytanym rekordzie było liczbą całkowitą / rzeczywistą, możesz mieć fragment 1-8 tego pola (8 zapisów rekordów / wierszy). Następnie zsumuj fragmenty po każdym zapisie do osobnego odnośnika odczytu. Umożliwia to równolegle do 8 jednoczesnych użytkowników.
Ponieważ pracujesz tylko z każdym fragmentem tworzącym częściową sumę, nie ma kolizji i prawdziwych równoległych aktualizacji (tzn. Piszesz blokować każdy fragment, a nie cały zunifikowany rekord odczytu). Działa to oczywiście tylko na polach numerycznych. Coś, co polega na modyfikacji matematycznej do przechowywania wyniku.
Zatem wiele fragmentów zapisu na zunifikowane pole odczytu na zunifikowany rekord odczytu. Te fragmenty liczbowe nadają się również do ECC, szyfrowania i transferu / przechowywania na poziomie bloków. Im więcej fragmentów zapisu, tym większe prędkości dostępu równoległego / współbieżnego zapisu danych nasyconych.
MMORPG cierpi z powodu tego problemu, gdy duża liczba graczy zaczyna się nawzajem uderzać umiejętnościami Obszar działania. Wszyscy ci gracze muszą pisać / aktualizować każdego innego gracza w tym samym czasie, równolegle, tworząc burzę blokującą wiersz zapisu w rekordach zunifikowanych graczy.
źródło