MySQL:事务与锁定表


110

我对事务与锁定表有些困惑,以确保数据库完整性,并确保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仅一次确保只有1个连接正在执行此操作,并在完成后将其解锁,但这似乎有点过头了。将事务包装在事务中是否会做同样的事情(确保没有其他连接会尝试同一进程而另一个仍在处理)?或将一个SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE更好?

Answers:


173

锁定表可防止其他数据库用户影响您锁定的行/表。但是,锁本身并不能确保您的逻辑处于一致状态。

想想一个银行系统。当您在线支付账单时,至少有两个受交易影响的帐户:从中提取资金的帐户。以及收款人的帐户,资金将汇入该帐户。还有银行的帐户,他们将愉快地存入交易中收取的所有服务费。鉴于(众所周知,这些天,人们)银行非常愚蠢,可以说他们的系统是这样的:

$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美元。根据最后完成的情况,您最终将在帐户中获得这两个余额中的任意一个,而不是应该以($ 100-$ 20-$ 30)结尾的$ 50。在这种情况下,“银行错误对您有利”

现在,假设您使用锁。您的帐单付款($ 20)首先达到目标,因此它赢了并锁定了您的帐户记录。现在您已经拥有专用权,可以从余额中扣除$ 20,然后将新的余额放回原位...您的帐户最终会得到$ 80的收益。但是...呃...您尝试去更新收款人的帐户,并且它的锁定时间和锁定时间超出了代码允许的范围,导致您的交易超时...我们正在处理愚蠢的银行,因此没有适当的错误处理时,代码只会拉一个exit(),而您的20美元消失在一团电子中。现在您已经花了20美元,但您仍欠接收者20美元,您的电话被收回。

所以...输入交易。您开始交易,从您的帐户中扣除$ 20,然后尝试向收款人存入$ 20 ...,然后又发生了爆破。但是这次,exit()代码可以代替rollback欺骗,您的$ 20被神奇地加回到了您的帐户中。

最后,归结为:

锁可以防止其他人干扰您正在处理的任何数据库记录。事务可以防止任何“较晚”的错误干扰您所做的“较早”的事情。谁也不能保证最后一切都会好起来。但是他们在一起。

在明天的课程中:僵局的喜悦。


4
我也/仍然感到困惑。假设接收者的帐户中有100美元开始,我们正在从我们的帐户中添加20美元的账单付款。我对事务的理解是,当它们开始时,任何事务内操作都将数据库视为处于事务开始时的状态。即:在我们进行更改之前,接收者帐户中有100美元。所以...当我们加上$ 20时,我们实际上将余额设为$ 120。但是,如果在我们交易期间有人将接收者的账户清零了$ 0,会发生什么情况?是否以某种方式阻止了它?他们会再次神奇地获得$ 120吗?这就是为什么还需要锁的原因吗?
罗斯,2010年

是的,这就是锁起作用的地方。一个适当的系统将对记录进行写锁定,以便在事务进行过程中没有其他人可以更新记录。偏执狂的系统会无条件地锁定记录,因此任何人也无法读取“过时”的余额。
Marc B 2010年

1
基本上,将事务视为保护代码路径内事物的安全。锁跨“并行”代码路径保护事物。直到遇到僵局……
Marc B 2010年

1
@MarcB,那么,如果仅使用事务已经可以确保锁定已到位,为什么还要显式进行锁定?甚至会出现这样的情况,即由于交易不足,我们必须进行显式锁定吗?
Pacerier 2014年

2
该答案不正确,可能导致错误的结论。这句话:“锁可以防止其他人干扰您正在处理的任何数据库记录。事务可以防止任何“后期”错误干扰您已经完成的“较早”操作。任何人都不能保证在计算机中一切正常。结束。但是他们在一起。” -会让您被解雇,这是非常错误和愚蠢的。请参见文章:en.wikipedia.org/wiki/ACID,en.wikipedia.org/wiki/Isolation_ ( database_systems)dev.mysql.com/doc/refman/5.1/ zh /…
Nikola Svitlica 2015年

14

您想要一个SELECT ... FOR UPDATESELECT ... 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


7

事务的概念和锁是不同的。但是,事务使用锁来帮助它遵循ACID原则。如果要在读取/写入时阻止该表同时读取/写入其他人,则需要使用锁来执行此操作。如果要确保数据完整性和一致性,则最好使用事务。我认为带有锁的事务中的隔离级别的混合概念。请搜索事务的隔离级别,SERIALIZE应该是您想要的级别。


这应该是正确的答案。锁定用于防止争用情况,而事务用于更新具有相关数据的多个表。尽管事务使用锁,但两个完全不同的概念。
Blue Water

6

在尝试IF NOT EXISTS ...执行a 然后执行an 时,我遇到了类似的问题,INSERT这在多个线程更新同一张表时导致了竞争状况。

我在这里找到了解决问题的方法:如何在标准SQL中编写INSERT IF NOT EXISTS查询

我意识到这并不能直接回答您的问题,但是执行检查和插入作为单个语句的相同原理非常有用;您应该可以对其进行修改以执行更新。


2

您对锁定和交易感到困惑。它们是RMDB中的两件事。锁定防止并发操作,而事务专注于数据隔离。请查看这篇出色的文章,以进行澄清和一些优美的解决方案。


1
锁可以防止其他人干扰您正在使用的记录,以简明扼要地描述其操作,而事务则可以防止以后的错误(其他人并行进行更改)干扰您以前的工作(通过回滚,以防有人进行某些操作)并行)几乎总结了交易...他对这些主题的理解感到困惑吗?
steviesama

1

我会用

START TRANSACTION WITH CONSISTENT SNAPSHOT;

首先,

COMMIT;

最后。

如果存储引擎支持事务(即InnoDB),则介于两者之间的任何操作都将与数据库的其他用户隔离开。


1
除了他从中选择的表之外,除非他专门锁定该表(或者直到他的UPDATE发生),否则它不会被锁定到其他会话,这意味着其他会话可以出现并在SELECT和UPDATE之间对其进行修改。
艾莉森·R.2010年

在阅读了MySQL文档中的“使用一致快照开始交易”之后,我看不到它实际上在哪里锁定了另一个连接以防止更新同一行。我的理解是,它将看到该表在事务开始时开始。因此,如果另一个事务正在进行中,已经获得一行并准备对其进行更新,则第二个事务在更新之前仍会看到该行。因此,它可能潜在地尝试更新其他事务将要更新的同一行。那是正确的还是我在进度中缺少什么?
瑞安

1
@Ryan它不做任何锁定;你是对的。锁定(是否锁定)取决于您执行的操作类型(SELECT / UPDATE / DELETE)。
艾莉森·R.2010年

4
我懂了。它确实使您自己的事务具有读取一致性,但不会阻止其他用户在您这样做之前修改行。
Martin Schapendonk 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.