2011-01-31 119 views
5

Tengo una aplicación impulsada por PHP/5.2 que utiliza transacciones en MySQL/5.1 para que pueda deshacer varias inserciones si se cumple una condición de error. Tengo diferentes funciones reutilizables para insertar diferentes tipos de elementos. Hasta aquí todo bien.Revertir transacciones con TABLAS DE BLOQUEO

Ahora necesito usar bloqueo de tabla para algunas de las inserciones. Como sugiere el manual oficial, estoy usando SET autocommit=0 en lugar de START TRANSACTION para que LOCK TABLES no emita una confirmación implícita. Y, tal como se documenta, el desbloqueo de las tablas implícitamente cometa cualquier transacción activa:

Y aquí está el problema: si simplemente evito UNLOCK TABLES, sucede que la segunda llamada a LOCK TABLES compromete cambios pendientes!

Parece que la única forma es realizar todos los pasos necesarios LOCK TABLES en una sola instrucción. Esa es una pesadilla de mantenimiento.

¿Este problema tiene una solución sensata?

aquí hay un pequeño script de prueba:

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

¿por qué necesita de bloqueo? ¿Cuál es el verdadero problema? –

+0

Necesito bloqueo para garantizar que solo un proceso pueda usar un número de secuencia para el año actual y no queden huecos en la secuencia. El verdadero problema es que MySQL confirma conjuntos de datos no validados automáticamente cuando intenta utilizar una función que no tiene en cuenta las transacciones, como el bloqueo de tablas, lo que supera el objetivo de usar transacciones. –

+1

No se puede usar SELECT .... PARA ACTUALIZAR; ? Funciona bien dentro de las transacciones, no hay problema en absoluto. Use un solo registro para la secuencia y actualice este registro cada vez. –

Respuesta

4

Al parecer, LOCK TABLES no se puede fijar para jugar bien con las transacciones. Una solución alternativa es reemplazarlo por SELECT .... FOR UPDATE. No necesita ninguna sintaxis especial (se puede usar regularmente START TRANSACTION) y funciona como se esperaba:

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 

Tenga en cuenta que COUNT(*) es sólo un ejemplo, normalmente se puede utilizar la instrucción SELECT para obtener los datos que realmente necesita ;-)

(Esta información fue suministrada por Frank Heikens.)

+2

Tenga en cuenta que SELECCIONAR ... PARA ACTUALIZAR aún permite que otras sesiones de base de datos lean de la tabla. Espero que esto ayude a alguien antes de que confíen en esta funcionalidad. –

Cuestiones relacionadas