2011-01-31 2 views
5

У меня есть приложение с PHP/5.2, которое использует транзакции в MySQL/5.1, чтобы он мог откатить несколько вставок, если выполнено условие ошибки. У меня есть разные функции повторного использования для вставки различных типов элементов. Все идет нормально.Откат транзакций с LOCK TABLES

Теперь мне нужно использовать блокировку таблиц для некоторых вставок. Как предлагает официальное руководство, я использую SET autocommit=0 вместо START TRANSACTION, поэтому LOCK TABLES не выдаёт неявный фиксатор. И, как описано, разблокирование таблицы неявно совершает любые активные сделки:

И здесь кроется проблема: если я просто избежать UNLOCK TABLES, случается так, что второй вызов LOCK TABLES совершает ожидающие изменения!

Похоже, что единственный способ - выполнить все необходимое LOCK TABLES в одном заявлении. Это главный кошмар.

Имеет ли этот вопрос разумное обходное решение?

Вот небольшой тестовый скрипт:

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

Почему вы должны блокировки? Какая проблема? –

+0

Мне нужна блокировка, чтобы гарантировать, что только один процесс может использовать порядковый номер для текущего года, и в последовательности не осталось пробелов. Реальная проблема заключается в том, что MySQL автоматически фиксирует неаудированные наборы данных, когда вы пытаетесь использовать функцию, которая не является транзакцией, например блокировка таблицы, тем самым обманывая всю точку использования транзакций. –

+1

Вы не можете использовать SELECT .... FOR UPDATE; ? Работает нормально в транзакциях, без проблем. Используйте одну запись для последовательности и обновляйте эту запись каждый раз. –

ответ

4

Видимо, LOCK TABLES не может быть исправлена, чтобы играть хорошо с транзакциями. Обходным путем является замена его на SELECT .... FOR UPDATE. Вам не нужно никакого специального синтаксиса (вы можете использовать регулярные START TRANSACTION), и она работает, как ожидалось:

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 

Пожалуйста, обратите внимание, что COUNT(*) это просто пример, вы можете нормально использовать ЗЕЬЕСТ для извлечения данных, которые вы на самом деле нужно ;-)

(Эта информация была предоставлена ​​Франк Heikens.)

+2

Обратите внимание, что SELECT ... FOR UPDATE по-прежнему позволяет другим сеансам базы данных читать из таблицы. Надеюсь, это поможет кому-то, прежде чем они будут полагаться на эту функциональность. –

Смежные вопросы