Mysql выберите для обновления - это не блокировка целевых строк. Как мне убедиться, что это так?

0

Таким образом, синтаксис для выбора для обновления - это что-то вроде

SELECT *     //1st query
FROM test
WHERE id = 4 FOR UPDATE;

UPDATE test    //2nd query
SET parent = 100
WHERE id = 4;

Я предполагаю, что фиксирующая часть - первая строка.

Поэтому, когда выполняется первый набор запросов, я не должен выбирать и изменять строку с id = 4 (кстати, это первичный ключ). Тем не менее, я все еще могу выбрать строку с id = 4 прежде чем обновлять что-либо, что может означать, что другой поток, возможно, войдет и попытается выбрать и обновить ту же строку перед ударами второй строки, что приведет к проблеме параллелизма.

Но когда я блокирую всю таблицу, как показано ниже

LOCK TABLES test WRITE;

Другие транзакции ожидаются и ждут, пока блокировка не будет отпущена. Единственная причина, по которой я хотел бы использовать SELECT FOR UPDATE вместо блокировки таблицы, - это причина, указанная здесь https://dev.mysql.com/doc/refman/5.7/en/lock-tables-and-transactions.html

Если я просто процитирую их здесь, это будет следующим:

LOCK TABLES плохо работает с транзакциями. Даже если вы используете синтаксис "SET autommit = 0", вы можете найти нежелательные побочные эффекты. Например, выдача второго запроса LOCK TABLES в транзакции будет COMMIT ваши ожидающие изменения:

SET autocommit=0;
LOCK TABLES foo WRITE;
INSERT INTO foo (foo_name) VALUES ('John');
LOCK TABLES bar WRITE; -- Implicit commit
ROLLBACK; -- No effect: data already committed

Во многих случаях LOCK TABLES можно заменить SELECT... FOR UPDATE, который полностью осведомлен о транзакции и не нуждается в специальном синтаксисе:

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

Поэтому, если я могу получить доступ к строкам, выбранным для обновления, перед тем, как произойдет фактическое обновление, что именно происходит с блокировкой SELECT FOR UPDATE? Также как я могу проверить, что строки заблокированы в моем приложении? (очевидно, что он не работает в первом наборе запросов, которые я написал)

Таблица создана с движком InnoDB

Франциско

Оба решения ниже приводят к тому, что родительский элемент равен 1

UPDATE test
SET parent = 99
WHERE id = 4;
COMMIT;

START TRANSACTION;
SELECT *
FROM test
WHERE id = 4 FOR UPDATE;
UPDATE test
SET parent = 98
WHERE id = 4;   //don't commit 

START TRANSACTION;
SELECT *
FROM test
WHERE parent = 98 FOR UPDATE; //commit did not happens so the id=4 document would still be parent = 99
UPDATE test
SET parent = 1
WHERE id = 4;
COMMIT;           //parent = 1 where id = 4

Другой просто меняет родительский условный код на 99 вместо 98

UPDATE test
SET parent = 99
WHERE id = 4;
COMMIT;

START TRANSACTION;
SELECT *
FROM test
WHERE id = 4 FOR UPDATE;
UPDATE test
SET parent = 98
WHERE id = 4;      //Don't commit

START TRANSACTION;
SELECT *
FROM test
WHERE parent = 99 FOR UPDATE;     //targets parent = 99 this time but id=4 still results in parent =1
UPDATE test
SET parent = 1
WHERE id = 4;
COMMIT;

Первые наборы запросов выполняются, как если бы документ id = 4 был перенумерован на parent = 98. Тем не менее, второй набор запросов выполняется так, как если бы id = 4 документ НЕ был передан parent = 99. Как мне поддерживать согласованность здесь?

  • 1
    Вы уверены, что таблицы созданы с InnoDB в качестве движка таблиц?
  • 0
    @RaymondNijland да его двигатель - InnoDB. Так что это поведение неожиданно верно?
Теги:
multithreading
locking

1 ответ

0
Лучший ответ

SELECT FOR UPDATE блокирует строку, выбранную для обновления, до тех пор, пока не завершится созданная вами транзакция. Другие транзакции могут читать только эту строку, но они не могут ее обновлять до тех пор, пока транзакция выбора для обновления остается открытой.

Чтобы заблокировать строку (строки):

START TRANSACTION;
SELECT * FROM test WHERE id = 4 FOR UPDATE;
# Run whatever logic you want to do
COMMIT;

Транзакция выше будет живой и заблокирует строку до ее фиксации.

Чтобы проверить это, есть разные способы. Я протестировал его с использованием двух терминальных экземпляров с открывшимся в каждом клиенте MySQL.

На first terminal вы запустите SQL:

START TRANSACTION;
SELECT * FROM test WHERE id = 4 FOR UPDATE;
# Do not COMMIT to keep the transaction alive

На second terminal вы можете попытаться обновить строку:

UPDATE test SET parent = 100 WHERE id = 4;

Поскольку вы создаете выбор для обновления на first terminal указанный выше запрос будет ждать, пока не будет совершена транзакция выбора для обновления, или будет тайм-аут.

Вернитесь к first terminal и совершите транзакцию:

COMMIT;

Проверьте second terminal и вы увидите, что был выполнен запрос на обновление (если он не был отключен).

  • 1
    Я обновил свой вопрос на основе вашего ответа

Ещё вопросы

Сообщество Overcoder
Наверх
Меню