2011-01-31 7 views
5

J'ai une application pilotée par PHP/5.2 qui utilise des transactions sous MySQL/5.1 afin de pouvoir annuler plusieurs insertions si une condition d'erreur est remplie. J'ai différentes fonctions réutilisables pour insérer différents types d'articles. Jusqu'ici tout va bien.Transactions rollback avec LOCK TABLES

Maintenant, j'ai besoin d'utiliser le verrouillage de table pour certains des inserts. Comme le suggère le manuel officiel, j'utilise SET autocommit=0 au lieu de START TRANSACTION alors LOCK TABLES n'émet pas de commit implicite. Et, comme documenté, déverrouillage des tables engage implicitement une transaction active:

Et est là le problème: si j'évite simplement UNLOCK TABLES, il arrive que le deuxième appel à LOCK TABLES engage les modifications en attente!

Il semble que le seul moyen est d'effectuer tous les LOCK TABLES nécessaires dans une seule instruction. C'est un cauchemar de maintenance.

Ce problème a-t-il une solution de contournement raisonnable?

Voici un petit script de 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

Pourquoi avez-vous besoin de verrouillage? Quel est le vrai problème? –

+0

J'ai besoin d'un verrouillage pour m'assurer qu'un seul processus est capable d'utiliser un numéro de séquence pour l'année en cours et qu'il ne reste aucun espace dans la séquence. Le vrai problème est que MySQL commet des ensembles de données non validés automatiquement lorsque vous essayez d'utiliser une fonctionnalité qui n'est pas consciente de la transaction, comme le verrouillage de table, ce qui annule tout l'intérêt d'utiliser des transactions. –

+1

Vous ne pouvez pas utiliser SELECT .... FOR UPDATE; ? Fonctionne bien dans les transactions, pas de problème du tout. Utilisez un seul enregistrement pour la séquence et mettez à jour cet enregistrement chaque fois. –

Répondre

4

Apparemment, LOCK TABLES ne peut pas être fixé à bien jouer avec les transactions. Une solution de contournement consiste à le remplacer par SELECT .... FOR UPDATE. Vous ne avez pas besoin de syntaxe particulière (vous pouvez utiliser START TRANSACTION régulière) et il fonctionne comme prévu:

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 

S'il vous plaît noter que COUNT(*) est juste un exemple, vous pouvez normalement utiliser l'instruction SELECT pour extraire des données que vous avez réellement besoin ;-)

(Ces informations ont été fournies par Frank Heikens.)

+2

Veuillez noter que SELECT ... FOR UPDATE permet toujours à d'autres sessions de base de données de lire à partir de la table. J'espère que cela aide quelqu'un avant qu'ils comptent sur cette fonctionnalité. –