2011-01-31 17 views
5

Posiadam aplikację opartą na PHP/5.2, która wykorzystuje transakcje w MySQL/5.1, więc może wycofać wiele wstawień, jeśli zostanie spełniony warunek błędu. Mam różne funkcje wielokrotnego użytku do wstawiania różnych typów przedmiotów. Jak na razie dobrze.Transakcje rollback z TABLAMAMI ZABLOKOWANIA

Teraz potrzebuję użyć blokady stołu dla niektórych wkładek. Jak sugeruje oficjalny podręcznik, używam SET autocommit=0 zamiast START TRANSACTION, więc LOCK TABLES nie wydaje niejawnego zatwierdzenia. I, co zostało udokumentowane, odblokowanie tabele domyślnie dopuszcza żadnej aktywnej transakcji:

I tu leży problem: jeśli po prostu uniknąć UNLOCK TABLES zdarza się, że drugie wywołanie LOCK TABLES zobowiązuje oczekujące zmiany!

Wydaje się, że jedynym sposobem jest wykonanie wszystkich niezbędnych LOCK TABLES w jednym wyciągu. To koszmar wieczny.

Czy ten problem ma sensowne obejście tego problemu?

Oto mały skrypt testowy:

DROP TABLE IF EXISTS test; 

CREATE TABLE test (
    test_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, 
    random_number INT(10) UNSIGNED NOT NULL, 
    PRIMARY KEY (test_id) 
) 
COLLATE='utf8_spanish_ci' 
ENGINE=InnoDB; 


-- No table locking: everything's fine 
START TRANSACTION; 
INSERT INTO test (random_number) VALUES (ROUND(10000* RAND())); 
SELECT * FROM TEST ORDER BY test_id; 
ROLLBACK; 
SELECT * FROM TEST ORDER BY test_id; 



-- Table locking: everything's fine if I avoid START TRANSACTION 
SET autocommit=0; 
INSERT INTO test (random_number) VALUES (ROUND(10000* RAND())); 
SELECT * FROM TEST ORDER BY test_id; 
ROLLBACK; 
SELECT * FROM TEST ORDER BY test_id; 
SET autocommit=1; 



-- Table locking: I cannot nest LOCK/UNLOCK blocks 
SET autocommit=0; 
LOCK TABLES test WRITE; 
INSERT INTO test (random_number) VALUES (ROUND(10000* RAND())); 
SELECT * FROM TEST ORDER BY test_id; 
ROLLBACK; 
UNLOCK TABLES; -- Implicit commit 
SELECT * FROM TEST ORDER BY test_id; 
SET autocommit=1; 


-- Table locking: I cannot chain LOCK calls ether 
SET autocommit=0; 
LOCK TABLES test WRITE; 
INSERT INTO test (random_number) VALUES (ROUND(10000* RAND())); 
SELECT * FROM TEST ORDER BY test_id; 
-- UNLOCK TABLES; 
LOCK TABLES test WRITE; -- Implicit commit 
INSERT INTO test (random_number) VALUES (ROUND(10000* RAND())); 
SELECT * FROM TEST ORDER BY test_id; 
-- UNLOCK TABLES; 
ROLLBACK; 
SELECT * FROM TEST ORDER BY test_id; 
SET autocommit=1; 
+0

dlaczego trzeba blokowania? Jaki jest prawdziwy problem? –

+0

Potrzebuję blokady, aby upewnić się, że tylko jeden proces jest w stanie użyć numeru sekwencji na bieżący rok i nie pozostawia żadnych przerw w sekwencji. Prawdziwy problem polega na tym, że MySQL automatycznie zatwierdza niezatwierdzone zestawy danych, gdy próbujesz użyć funkcji, która nie jest świadoma transakcji, takiej jak blokowanie tabel, co powoduje, że cały punkt wykorzystania transakcji jest bity. –

+1

Nie można użyć opcji WYBIERZ ... DO AKTUALIZACJI; ? Działa dobrze w ramach transakcji, bez problemu. Użyj pojedynczego rekordu dla sekwencji i aktualizuj ten rekord za każdym razem. –

Odpowiedz

4

Najwyraźniej LOCK TABLES nie może być ustalona na dobrze bawić się z transakcji. Rozwiązaniem jest zastąpienie go przez SELECT .... FOR UPDATE. Nie potrzeba żadnej specjalnej składni (można użyć zwykłego START TRANSACTION) i działa zgodnie z oczekiwaniami:

START TRANSACTION; 
SELECT COUNT(*) FROM foo FOR UPDATE; -- Lock issued 
INSERT INTO foo (foo_name) VALUES ('John'); 
SELECT COUNT(*) FROM bar FOR UPDATE; -- Lock issued, no side effects 
ROLLBACK; -- Rollback works as expected 

Uwaga COUNT(*) to tylko przykład, można normalnie korzystać z SELECT, aby pobrać dane rzeczywiście potrzebne ;-)

(Ta informacja została dostarczona przez Franka Heikens.)

+2

Należy pamiętać, że SELECT ... FOR UPDATE nadal zezwala na odczyt z tabeli innych sesji bazy danych. Mam nadzieję, że pomoże to komuś, zanim będzie polegać na tej funkcjonalności. –