MySQL:事务与锁定表

Rya*_*yan 102 mysql sql locking transactions

我对事务与锁定表有点混淆,以确保数据库完整性,并确保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) ...
}
Run Code Online (Sandbox Code Playgroud)

我需要确保没有其他查询会干扰并执行相同的操作SELECT(在连接完成更新行之前读取'旧值'.

我知道我可以默认LOCK TABLES table只确保一次只有一个连接正在执行此操作,并在完成后解锁它,但这看起来有点矫枉过正.在事务中包装它会做同样的事情(确保没有其他连接尝试相同的进程而另一个仍处理)?或者会更好SELECT ... FOR UPDATE还是SELECT ... LOCK IN SHARE MODE更好?

Mar*_*c B 160

锁定表可防止其他数据库用户影响您锁定的行/表.但锁定本身并不能确保您的逻辑以一致的状态出现.

想想银行系统.当您在线支付账单时,至少有两个账户受到交易的影响:您的账户,从中获取资金.接收方的账户,资金转入其中.银行的账户,他们将愉快地存入交易所收取的所有服务费.鉴于(如众所周知的那样)银行非常愚蠢,让我们说他们的系统是这样的:

$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
Run Code Online (Sandbox Code Playgroud)

现在,由于没有锁定和交易,该系统容易受到各种竞争条件的影响,其中最大的一种是在您的帐户上执行多次付款,或者是并行接收方的帐户.虽然您的代码已检索到您的余额并且正在执行huge_overdraft_fees()等等,但完全有可能其他一些付款将并行运行相同类型的代码.他们将检索你的余额(比如说,100美元),做他们的交易(取出你付的20美元,以及30美元他们让你搞砸了),现在两个代码路径有两个不同的余额:80美元和$ 70 根据最后的结果,你最终会得到你帐户中的两个余额中的任何一个,而不是你最终应该得到的50美元(100美元 - 20美元 - 30美元).在这种情况下,"银行错误对你有利"

现在,假设你使用锁.您的账单支付(20美元)首先击中管道,因此它赢得并锁定您的账户记录.现在你已经独家使用了,可以从余额中扣除20美元,然后平衡地写下新的余额......你的账户最终会达到预期的80美元.但是......呃......你试图更新接收者的账号,它被锁定,锁定的时间超过了代码允许的时间,超时了你的交易...我们正在处理愚蠢的银行,所以没有正确的错误处理,代码只是拉了一个exit(),你的20美元就消失了一阵电子.现在你已经花了20美元,你仍然欠接收器20美元,你的电话被收回了.

所以...输入交易.你开始一笔交易,你从你的帐户中扣除了20美元,你试图用20美元的价格来记录接收器......然后再次爆炸了.但是这一次,而不是exit()代码可以做rollback,而且,你的20美元可以神奇地添加回你的账户.

最后,归结为:

锁定会阻止任何其他人干扰您正在处理的任何数据库记录.交易使任何"后期"错误不会干扰您已完成的"早期"事情.两者都不能保证事情最终成功.但他们在一起.

明天的教训:僵局的喜悦.

  • 我还/仍然困惑.假设接收者账户中有100美元,我们将从我们的账户中添加20美元的账单.我对事务的理解是,当它们启动时,任何事务内操作都会看到数据库处于事务开始时的状态.即:直到我们改变它,接收者帐户有100美元.所以...当我们加20美元时,我们实际上设定了120美元的余额.但是,如果在我们的交易过程中有人将接收方帐户耗尽到0美元,会发生什么?这是以某种方式阻止的吗?他们神奇地再次获得120美元?这也是为什么需要锁? (3认同)
  • 这个答案是不正确的,可能会导致错误的结论。这条语句:“锁可以防止其他任何人干扰您正在处理的任何数据库记录。事务可以防止任何“后期”错误干扰您已经完成的“早期”事情。任何一方都不能保证事情在结束。但他们在一起。” - 会让你被解雇,这是非常错误和愚蠢的参见文章:https://en.wikipedia.org/wiki/ACID、https://en.wikipedia.org/wiki/Isolation_(database_systems) 和 https:// dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html (2认同)

bra*_*w98 27

出于与您在问题中指出的相同原因,我已经开始研究同一主题。我对 SO 中给出的答案感到困惑,因为它们是部分答案并且没有提供大局。在阅读了来自不同 RDMS 提供商的几个文档页面后,以下是我的看法:

交易

语句是数据库命令,主要是读取和修改数据库中的数据。事务是单个或多个语句执行的范围。他们提供两件事:

  1. 一种机制,保证事务中的所有语句都正确执行,或者在发生单个错误的情况下,这些语句修改的任何数据都将恢复到其最后的正确状态(即回滚)。这种机制提供的称为原子性
  2. 一种保证并发读取语句可以查看数据而不会发生下述部分或全部现象的机制。

脏读:事务读取并发未提交事务写入的数据。

不可重复读:事务重新读取其先前读取的数据,并发现数据已被另一个事务(自初始读取以来已提交的事务)修改。

幻读:事务重新执行查询,返回满足搜索条件的行集,并发现满足条件的行集由于另一个最近提交的事务而发生了更改。

序列化异常:成功提交一组事务的结果与一次运行这些事务的所有可能顺序不一致。

这种机制提供的功能称为隔离,而让语句选择事务中不应发生哪些现象的机制称为隔离级别

作为示例,这是 PostgreSQL 的隔离级别/现象表: 在此输入图像描述

如果数据库系统违反了任何所描述的承诺,则更改将回滚并通知调用者。

下面描述了如何实施这些机制来提供这些保证。

锁类型

  1. 排它锁:当在某个资源上获取排它锁时,无法在该资源上获取其他排它锁。排他锁始终在修改语句(INSERT、UPDATE 或 DELETE)之前获取,并在事务完成后释放。要在修改语句之前显式获取独占锁,您可以使用 FOR UPDATE(PostgreSQL, MySQL) 或 UPDLOCK (T-SQL) 等提示。
  2. 共享锁:可以在一个资源上获取多个共享锁。但是,一个资源上不能同时获取共享锁和排它锁。根据隔离级别的数据库实现,在读语句(SELECT、JOIN)之前可能会或可能不会获取共享锁。

锁定资源范围

  1. Row:语句执行的单行。
  2. 范围:基于语句(SELECT ... WHERE)中给出的条件的特定范围。
  3. 表:整个表。(主要用于防止批量更新等大语句出现死锁。)

作为 SQL-Server 不同隔离级别的默认共享锁行为的示例: 在此输入图像描述

僵局

锁定机制的缺点之一是死锁。当一条语句进入等待状态时,就会发生死锁,因为所请求的资源被另一个等待语句持有,而该资源又在等待另一个等待语句持有的另一个资源。在这种情况下,数据库系统检测到死锁并终止其中一个事务。不小心使用锁会增加死锁的可能性,但即使没有人为错误,死锁也可能发生。

快照(数据版本控制)

这是一种隔离机制,它为语句提供在特定时间获取的数据的副本。

  1. 语句开始:为语句执行开始时获取的语句提供数据复制。它还通过保留这些数据直到事务完成来帮助回滚机制。

  2. 事务开始:为事务开始时的语句提供数据复制。

所有这些机制共同提供了一致性

当谈到乐观锁和悲观锁时,它们只是对并发问题方法分类的命名。

悲观并发控制:

锁系统可防止用户以影响其他用户的方式修改数据。当用户执行导致应用锁的操作后,其他用户无法执行与锁冲突的操作,直到所有者释放锁为止。这称为悲观控制,因为它主要用于数据争用较多的环境,在这种环境中,使用锁保护数据的成本小于发生并发冲突时回滚事务的成本。

乐观并发控制:

在乐观并发控制中,用户在读取数据时不会锁定数据。当用户更新数据时,系统会检查其他用户在读取数据后是否更改了数据。如果其他用户更新了数据,则会引发错误。通常,收到错误的用户会回滚事务并重新开始。这称为乐观,因为它主要用于数据争用较低的环境,并且偶尔回滚事务的成本低于读取时锁定数据的成本。

例如,默认情况下,PostgreSQL 使用快照来确保读取的数据没有更改,如果更改则回滚,这是一种乐观的方法。然而,SQL-Server 默认使用读锁来提供这些承诺。

实施细节可能会根据您选择的数据库系统而变化。然而,根据数据库标准,他们需要使用这些机制以一种或另一种方式提供这些规定的事务保证。如果您想了解有关该主题或具体实施细节的更多信息,下面是一些对您有用的链接。

  1. SQL-Server - 事务锁定和行版本控制指南
  2. PostgreSQL - 事务隔离
  3. PostgreSQL - 显式锁定
  4. MySQL - 一致的非锁定读取
  5. MySQL-锁定
  6. 了解隔离级别(视频)


Ali*_* R. 14

正如你所说,你想要一个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


Ton*_*ony 6

尝试a时遇到类似的问题IF NOT EXISTS ...,然后INSERT在多个线程更新同一个表时执行导致竞争条件的问题.

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

我意识到这并没有直接回答你的问题,但是执行检查和插入作为单个语句的相同原则非常有用; 您应该能够修改它以执行更新.


小智 5

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