2011-01-31 5 views
5

Possiedo un'applicazione basata su PHP/5.2 che utilizza transazioni in MySQL/5.1 in modo da poter eseguire il rollback di più inserti se si verifica una condizione di errore. Ho diverse funzioni riutilizzabili per inserire diversi tipi di elementi. Fin qui tutto bene.Transazioni di rollback con LOCK TABLES

Ora ho bisogno di utilizzare il blocco della tabella per alcuni degli inserti. Come suggerisce il manuale ufficiale, sto utilizzando SET autocommit=0 anziché START TRANSACTION quindi LOCK TABLES non emette un commit implicito. E, come documentato, sbloccando tavoli impegna implicitamente qualsiasi transazione attiva:

E qui sta il problema: se ho semplicemente evito UNLOCK TABLES, accade che la seconda chiamata a LOCK TABLES impegna modifiche in sospeso!

Sembra che l'unico modo sia eseguire tutto il necessario LOCK TABLES in una singola istruzione. Questo è un incubo di Mainteinance.

Questo problema risolve il problema?

Ecco un piccolo script di test:

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

perché avete bisogno di blocco? Qual è il vero problema? –

+0

Ho bisogno di un blocco per garantire che solo un processo sia in grado di utilizzare un numero di sequenza per l'anno corrente e che non rimangano interruzioni nella sequenza. Il vero problema è che MySQL impegna automaticamente i set di dati non convalidati quando si tenta di utilizzare una funzionalità che non è consapevole delle transazioni come il blocco delle tabelle, superando così l'intero punto dell'utilizzo delle transazioni. –

+1

Non puoi usare SELECT .... FOR UPDATE; ? Funziona bene nelle transazioni, nessun problema. Usa un singolo record per la sequenza e aggiorna questo record ogni volta. –

risposta

4

A quanto pare, LOCK TABLES non può essere fissato a giocare bene con le transazioni. Una soluzione alternativa è sostituirla con SELECT .... FOR UPDATE. Non hai bisogno di una sintassi speciale (è possibile utilizzare normale START TRANSACTION) e funziona come previsto:

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 

prega di notare che COUNT(*) è solo un esempio, normalmente è possibile utilizzare l'istruzione SELECT per recuperare i dati è effettivamente necessario ;-)

(Questa informazione è stata fornita da Frank Heikens.)

+2

Si noti che SELEZIONA ... PER AGGIORNAMENTO consente comunque alle altre sessioni del database di leggere dalla tabella. Spero che questo aiuti qualcuno prima che facciano affidamento su questa funzionalità. –