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 чтобы убедиться, что только 1 подключение в время, и разблокировать его, когда я закончу, но это кажется перебор. Будет ли обертывание этого в транзакции делать то же самое (гарантируя, что никакое другое соединение не пытается выполнить тот же процесс, пока другой все еще обрабатывает)? Или будет SELECT ... FOR UPDATE или SELECT ... LOCK IN SHARE MODE быть лучше?
6 ответов:
блокировка таблиц не позволяет другим пользователям БД влиять на заблокированные строки/таблицы. Но замки сами по себе не гарантируют, что ваша логика выйдет в согласованном состоянии.
подумайте о банковской системе. Когда вы оплачиваете счет онлайн, есть по крайней мере два счета, затронутые транзакцией: ваш счет, с которого берутся деньги. И счет получателя, на который перечисляются деньги. И банковский счет, на который они с радостью внесут все плата за обслуживание, взимаемая по сделке. Учитывая (как все знают в наши дни), что банки чрезвычайно глупы, скажем, их система работает следующим образом:
$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теперь, без блокировок и без транзакций, эта система уязвима к различным условиям гонки, самым большим из которых является несколько платежей, выполняемых на вашем счете или на счете получателя параллельно. В то время как ваш код имеет свой баланс восстановлен и делает huge_overdraft_fees() и еще много чего, это вполне возможно, что какой-то другой платеж будет работать с тем же типом кода параллельно. Они будут восстанавливать ваш баланс (скажем, $100), делать свои транзакции (вынимайте $20, которые вы платите, и $30, которые они вас обманывают), и теперь оба пути кода имеют два разных баланса: $80 и $70. В зависимости от того, какие из них заканчиваются последними, вы получите любой из этих двух остатков на своем счете вместо $ 50, которые вы должны были получить ($100 - $20 - $30). В данном случае - "банк ошибка в вашу пользу".
теперь, допустим, вы используете замки. Ваш платеж по счету ($20) попадает в трубу первым, поэтому он выигрывает и блокирует запись вашего счета. Теперь у вас есть эксклюзивное использование, и вы можете вычесть $20 из баланса, и написать новый баланс обратно в мире... и ваш счет заканчивается с $ 80, как и ожидалось. Но... ох... Вы пытаетесь обновить учетную запись получателя, и она заблокирована, и заблокирована дольше, чем позволяет код, тайм-аут вашей транзакции... Мы имеем дело с тупицей банки, поэтому вместо правильной обработки ошибок, код просто тянет
exit(), и ваши $ 20 исчезают в облаке электронов. Теперь у вас нет $20, и вы все еще должны $20 приемнику, и ваш телефон будет возвращен.так... ввод проводок. Вы начинаете транзакцию, вы дебетуете свой счет $20, вы пытаетесь кредитовать получателя с $20... и снова что-то взрывается. Но на этот раз, вместо
exit(), код может просто сделатьrollback, и пуф, ваши $ 20 волшебно добавлено обратно в ваш аккаунт.В конце концов, это сводится к следующему:
блокировки удерживают кого-либо еще от вмешательства в любые записи базы данных, с которыми вы имеете дело. Транзакции удерживают любые "более поздние" ошибки от вмешательства в" более ранние " вещи, которые вы сделали. Ни один из них не может гарантировать, что все будет хорошо в конце концов. Но вместе они делают это.
в завтрашнем уроке: радость тупиков.
вы хотите
SELECT ... FOR UPDATEилиSELECT ... LOCK IN SHARE MODEвнутри транзакции, как вы сказали, так как обычно выбирает, независимо от того, находятся ли они в транзакции или нет, не будет блокировать таблицу. Какой из них вы выберете, будет зависеть от того, хотите ли вы, чтобы другие транзакции могли читать эту строку во время выполнения транзакции.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
у меня была аналогичная проблема при попытке
IF NOT EXISTS ...а затем выполнениеINSERTчто вызвало состояние гонки, когда несколько потоков обновляли одну и ту же таблицу.Я нашел решение проблемы здесь: Как написать INSERT, если не существует запросов в стандартном SQL
Я понимаю, что это не дает прямого ответа на ваш вопрос, но тот же принцип выполнения проверки и вставки в виде одного утверждения очень полезен; вы должны быть в состоянии измените его, чтобы выполнить обновление.
понятия транзакций и блокировки различны. Однако транзакция использовала блокировки, чтобы помочь ей следовать принципам ACID. Если вы хотите, чтобы таблица не позволяла другим читать/писать в то же время, когда вы читаете / пишете, вам нужна блокировка для этого. Если вы хотите убедиться в целостности и согласованности данных, вам лучше использовать транзакции. Я думаю, что смешанные понятия уровней изоляции в транзакциях с замками. Пожалуйста, найдите уровни изоляции транзакций, сериализация должна быть уровень вы хотите.
вы путаете с блокировкой и транзакцией. Это две разные вещи в RMDB. Блокировка предотвращает параллельные операции, в то время как транзакция фокусируется на изоляции данных. Проверьте этой отличная статья для разъяснения и некоторого изящного решения.
Я бы использовать
START TRANSACTION WITH CONSISTENT SNAPSHOT;для начала, и A
COMMIT;до конца.
все, что вы делаете изолирован от других пользователей вашей базы данных если ваш механизм хранения поддерживает транзакции (что является InnoDB).
Comments