2010-11-19 4 views
84

Я немного смущен транзакциями и столами для обеспечения целостности базы данных и убедитесь, что SELECT и UPDATE остаются в синхронизации, и никакое другое соединение не вмешивается в нее. Мне нужно:MySQL: транзакции против таблиц блокировки

SELECT * FROM table WHERE (...) LIMIT 1 

if (condition passes) { 
    // Update row I got from the select 
    UPDATE table SET column = "value" WHERE (...) 

    ... other logic (including INSERT some data) ... 
} 

мне нужно убедиться, что никакие другие запросы не будут мешать и выполнять те же SELECT (чтение «старое значение» до этой связи завершает обновление строки

Я знаю, что могу по умолчанию. до LOCK TABLES table, чтобы убедиться, что только одно соединение делает это за раз, и разблокируйте его, когда я закончил, но это похоже на излишний. Будет ли завершение того, что в транзакции делает то же самое (при отсутствии других попыток подключения процесс, в то время как другой все еще обрабатывается)? Или будет лучше SELECT ... FOR UPDATE или SELECT ... LOCK IN SHARE MODE?

ответ

124

Блокировка таблицы не позволяет другим пользователям БД от воздействия на строки/таблицы вы заблокированы. Но блокировки сами по себе НЕ гарантируют, что ваша логика выйдет в согласованном состоянии.

Подумайте о банковской системе.Когда вы оплачиваете онлайн-счет, на транзакцию влияет как минимум две аккаунта: ваша учетная запись, из которой берутся деньги. И счет получателя, в который перечисляются деньги. И счет банка, в который они с радостью будут перечислять все комиссионные, взимаемые с транзакции. Принимая во внимание (как всем известно, в эти дни), что банки чрезвычайно глупо, скажем, их система работает следующим образом:

$balance = "GET BALANCE FROM your ACCOUNT"; 
if ($balance < $amount_being_paid) { 
    charge_huge_overdraft_fees(); 
} 
$balance = $balance - $amount_being paid; 
UPDATE your ACCOUNT SET BALANCE = $balance; 

$balance = "GET BALANCE FROM receiver ACCOUNT" 
charge_insane_transaction_fee(); 
$balance = $balance + $amount_being_paid 
UPDATE receiver ACCOUNT SET BALANCE = $balance 

Теперь, без замков и без каких-либо операций, эта система является уязвимой к различным условиям гонки, самый большой из который представляет собой несколько платежей, выполняемых в вашей учетной записи, или учетную запись получателя параллельно. В то время как ваш код имеет ваш баланс, полученный и делающий огромный_overdraft_fees() и еще много чего, вполне возможно, что некоторые другие платежи будут работать с одним и тем же типом кода параллельно. Они будут получать ваш баланс (скажем, 100 долларов США), совершают свои транзакции (вынимают 20 долларов США, которые вы платите, и 30 долларов, за которые вы их заманиваете), и теперь оба пути кода имеют два разных баланса: 80 долларов США и $ 70. В зависимости от того, какие из них заканчиваются последним, вы получите один из двух балансов в своей учетной записи, вместо 50 долларов США, которые вы должны были получить (от $ 100 до $ 20 - $ 30). В этом случае «банковская ошибка в вашу пользу».

Теперь предположим, что вы используете блокировки. Сначала вы получаете платеж по счету (20 долларов США), поэтому он выигрывает и блокирует вашу учетную запись. Теперь у вас есть эксклюзивное использование, и вы можете вычесть 20 долларов из баланса, и снова записать новый баланс ... и ваша учетная запись заканчивается на 80 долларов, как ожидается. Но ... ухо ... Вы пытаетесь обновить учетную запись получателя, и она заблокирована и заблокирована дольше, чем позволяет код, тайминг вашей транзакции ... Мы имеем дело с глупыми банками, поэтому вместо правильной ошибки обработка, код просто тянет exit(), а ваши 20 долларов исчезают в клубок электронов. Теперь у вас 20 долларов, и вы все равно должны 20 долларов за приемник, и ваш телефон получает обратно.

Итак ... введите транзакции. Вы начинаете транзакцию, вы дебетеваете свою учетную запись на 20 долларов США, вы пытаетесь кредитовать получателя с помощью 20 долларов ... и что-то снова взрывается. Но на этот раз, вместо exit(), код может просто сделать rollback, а пуф, ваши $ 20 волшебным образом добавлены обратно в вашу учетную запись.

В конце концов, сводится к следующему:

Замки держать кого-либо еще от вмешательства каких-либо записей в базе данных вы имеете дело с. Транзакции содержат любые «более поздние» ошибки от вмешательства в «ранние» вещи, которые вы сделали. Ни один из них не может гарантировать, что в итоге все будет хорошо. Но вместе они делают.

в уроке завтрашнего дня: Радость тупиков.

+2

Я тоже все еще смущен. Скажем, у учетной записи приемника было 100 долларов, чтобы начать, и мы добавляем платеж в размере 20 долларов США из нашей учетной записи. Мое понимание транзакций заключается в том, что при их запуске любая операция в транзакции видит базу данных в состоянии, которое было в начале транзакции. т.е.: пока мы его не изменим, учетная запись получателя составляет 100 долларов США. Итак ... когда мы добавляем $ 20, мы фактически устанавливаем баланс в 120 долларов. Но что произойдет, если во время нашей транзакции кто-то спустил учетную запись приемника до 0 долларов? Это как-то предотвращено? Они волшебным образом получают 120 долларов снова? Именно поэтому нужны замки? – Russ

+0

Да, вот где затворы вступают в игру. Правильная система блокирует запись записи, чтобы никто другой не мог обновить запись во время выполнения транзакции. Параноидальная система поставила бы безусловную блокировку записи, чтобы никто не мог прочитать «устаревший» баланс. –

+1

В основном посмотрите на транзакции как на защиту вещей внутри вашего кода. Блокирует безопасные вещи через «параллельные» кодовые пути. До тупиков ... –

0

Я бы использовать

START TRANSACTION WITH CONSISTENT SNAPSHOT; 

, чтобы начать с, и

COMMIT; 

заканчиваться.

Все, что вы делаете между ними, изолировано от других пользователей вашей базы данных , если ваш механизм хранения поддерживает транзакции (это InnoDB).

+1

За исключением того, что он выбирает, он не будет заблокирован для других сеансов, если он специально не заблокирует его (или пока не произойдет его ОБНОВЛЕНИЕ), что означает, что другие сеансы могут прийти и изменить его между SELECT и UPDATE. –

+0

После ознакомления с START TRANSACTION WITH CONSISTENT SNAPSHOT в документации MySQL я не вижу, где он фактически блокирует другое соединение, обновляя ту же строку. Я понимаю, что он увидит, однако, что таблица началась в начале транзакции. Поэтому, если выполняется другая транзакция, она уже получила строку и собирается ее обновить, вторая транзакция по-прежнему будет видеть строку до ее обновления. Это может потребоваться, чтобы попытаться обновить ту же строку, о которой идет другая транзакция. Это правильно или я что-то пропустил в ходе? – Ryan

+1

@Ryan Он не фиксирует; ты прав. Блокировка (или нет) определяется типом выполняемых операций (SELECT/UPDATE/DELETE). –

6

У меня была аналогичная проблема при попытке выполнить IF NOT EXISTS ..., а затем выполнить INSERT, что вызвало гонку, когда несколько потоков обновляли одну и ту же таблицу.

Я нашел решение проблемы здесь: How to write INSERT IF NOT EXISTS queries in standard SQL

Я понимаю, что это не прямо ответить на ваш вопрос, но тот же принцип выполнения проверки и вставить в одно заявление очень полезно; вы должны иметь возможность изменить его, чтобы выполнить обновление.

+1

+1 для отличной связанной статьи, независимой от платформы. – Russ

13

Вы хотите, чтобы в транзакции, как вы сказали, SELECT ... FOR UPDATE или SELECT ... LOCK IN SHARE MODE, так как обычно SELECT, независимо от того, находятся они в транзакции или нет, не будут блокировать таблицу. Какой из них вы выберете, зависит от того, хотите ли вы, чтобы другие транзакции могли читать эту строку во время вашей транзакции.

http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html

START TRANSACTION WITH CONSISTENT SNAPSHOT не будет делать трюк для вас, как и другие операции могут еще прийти и изменить эту строку. Это указано прямо в верхней части ссылки ниже.

Если другие сессии одновременно обновления та же таблица [...] вы можете увидеть таблицу в состоянии, которое никогда не существовало в базе данных.

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

2

Вы путаете с блокировкой & сделка. В RMDB это две разные вещи. Блокировка предотвращает параллельные операции, в то время как транзакция фокусируется на изоляции данных. Проверьте this отличную статью для разъяснения и изящного решения.

+0

Замки, препятствующие другим мешать записи, с которыми вы работаете, описывают, что она делает кратко, а транзакции предотвращают последующие ошибки (другие из которых делают параллельные изменения) от вмешательства в предыдущие действия, которые вы сделали (путем откат в случае, если кто-то сделал что-то параллельно) в значительной степени суммирует транзакции ... что смущает его понимание этих тем? – steviesama

2

Концепции транзакций и замки разные. Однако транзакция использовала блокировки, чтобы помочь ей следовать принципам ACID. Если вы хотите, чтобы таблица не позволяла другим читать или писать в то же время, когда вы читаете/пишете, для этого вам нужен замок. Если вы хотите убедиться, что целостность и консистенция данных, вам лучше использовать транзакции. Я думаю, что смешанные концепции уровней изоляции в транзакциях с замками. Просматривайте уровни изоляции транзакций, SERIALIZE должен быть нужного вам уровня.

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