MySQL:事务与locking表
我对事务与locking表有点混淆,以确保数据库的完整性,并确保SELECT和UPDATE保持同步,没有其他连接干扰它。 我需要:
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
更好?
locking表可防止其他数据库用户影响您locking的行/表。 但是锁本身并不能保证你的逻辑以一致的状态出现。
想想银行系统。 当您在网上支付账单时,至less有两个账户受到交易的影响:您的账户,从中取钱。 而收款人的账户,汇款。 还有银行的账户,他们将愉快地存入交易中收取的所有服务费。 鉴于(现在大家都知道)银行是非常愚蠢的,假设他们的系统是这样工作的:
$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()以及其他操作时,其他付款完全可能会同时运行相同types的代码。 他们会找回你的余额(比如说100美元),做他们的交易(拿出20美元你付的钱,还有30美元他们把你搞砸了),现在两个代码path有两个不同的余额:80美元和$ 70 取决于哪一个最后完成,您将最终与您的帐户中的这两个余额中的任何一个,而不是50美元,你应该已经结束了($ 100 – $ 20 – $ 30)。 在这种情况下,“银行错误对你有利”。
现在,假设你使用锁。 您的账单支付($ 20)首先到达pipe道,因此赢得并locking您的账户logging。 现在你已经有了独家使用,并且可以从余额中扣除20美元,然后把新的余额重新写回来,而且你的帐户最终如预期的那样达到了80美元。 但是…呃…你试着去更新接收者的帐户,并且它被locking,并被locking的时间超过了代码允许的时间,超时你的交易…我们正在处理愚蠢的银行,所以没有适当的error handling,代码只是拉exit()
,你的$ 20消失成一团电子。 现在你拿出20美元,而你还欠着20美元的收款机,而你的电话就被收回了。
所以…input交易。 你开始了一个交易,你从你的账户中扣除了20美元,你试图用20美元的价格扣除收款人的费用。 但是这一次,代码不是exit()
,而是代码可以rollback
,poof,你的$ 20被神奇地添加回你的账户。
最后归结为:
锁使其他人不会干扰您正在处理的任何数据库logging。 事务处理使得任何“以后”的错误都不会干扰你所做的“早期”事情。 没有一个人可以保证事情最终成功。 但是在一起,他们呢。
在明天的课上:僵局的喜悦。
你需要一个SELECT ... FOR UPDATE
或SELECT ... LOCK IN SHARE MODE
在一个事务中的SELECT ... LOCK IN SHARE MODE
,正如你所说的,因为通常的SELECT,无论它们是否在事务中,都不会locking一个表。 您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
当尝试一个IF NOT EXISTS ...
时遇到了类似的问题,然后执行一个INSERT
,当多个线程正在更新同一个表时,这个INSERT
导致竞争条件。
我在这里find了解决问题的办法: 如何在标准SQL中编写INSERT IF NOT EXISTS查询
我意识到这并不直接回答你的问题,但执行检查和插入作为一个单一的陈述相同的原则是非常有用的; 您应该可以修改它来执行更新。
你与locking和交易混淆。 在RMDB中它们是两个不同的东西。 locking可防止并发操作,而事务则着重于数据隔离。 看看这个伟大的文章澄清和一些优雅的解决scheme。
交易概念和locking是不同的。 但是,事务使用锁来帮助它遵循ACID原则。 如果要在读/写时阻止其他人在同一时间点读/写数据,则需要使用锁来执行此操作。 如果你想确保数据的完整性和一致性,你最好使用交易。 我认为与锁具交易的隔离级别混合概念。 请search隔离级别的交易,SERIALIZE应该是你想要的级别。
我会用一个
START TRANSACTION WITH CONSISTENT SNAPSHOT;
开始,和一个
COMMIT;
以…结束。
如果您的存储引擎支持交易 (即InnoDB),那么您所做的任何操作都与数据库的其他用户隔离。