什么是SQL中的SELECT FOR UPDATE(有例子)?

502 阅读4分钟

关系型数据库对于事务性工作负载来说是很好的。但是,当多个事务开始试图同时访问相同的数据时,事情就会变得很混乱。幸运的是,在许多SQL数据库中,有一个解决方案:SELECT FOR UPDATE

SELECT FOR UPDATE 是一个SQL命令,在事务性工作负载的背景下很有用。它允许你 "锁定 "由 查询返回的行,直到该查询所属的整个事务被提交。其他试图访问这些行的事务被放置在一个基于时间的队列中等待,并在第一个事务完成后按时间顺序执行。SELECT

这是很有用的,因为它可以防止在多个事务试图读取这些相同的行时,出现激动和不必要的事务重试现象。任何时候,当多个事务可能在大致相同的时间内处理相同的行时,SELECT FOR UPDATE ,以增加吞吐量和减少尾部延迟(与你不使用它的情况相比)。

换句话说:SELECT FOR UPDATE 使争夺的事务处理得更顺利(这通常也意味着它们处理得更快更有效)。

一般来说,SELECT FOR UPDATE 对于任何事务性工作负载都是有用的,在这些工作负载中,多个事务可能试图在同一时间访问同一行。

然而,不同 "口味 "的SQL处理SELECT FOR UPDATE ,有些则完全不支持它。例如,MySQL支持SELECT FOR UPDATE,但SQLite不支持

通常情况下,其原因与不同系统处理事务的方式有关。例如,SQLite不需要SELECT FOR UPDATE ,因为启动一个事务会锁定整个数据库。这使得SQLite数据库能够提供可序列化的事务隔离,这是最高级别的隔离,以实现铁一般的一致性。然而,在一个可能只影响几条记录的事务中锁定整个数据库,对性能有明显的负面影响,特别是在规模上。

然而,在性能和一致性之间做出选择是没有必要的。例如,CockroachDB提供了可序列化的隔离,但是它不需要在事务开始时锁定整个数据库来实现这一点。SELECT FOR UPDATE ,在有并发事务处理相同行的情况下,可以使用数据库性能最大化,而最终结果(在CockroachDB的情况下)仍然是一个具有可序列化隔离的数据库。

不同的SQL数据库处理事务隔离的方式不同,因此SELECT FOR UPDATE ,所以熟悉你所使用的系统的选项和默认值是很重要的。

让我们来看看SELECT FOR UPDATE 是如何工作的。在这里我们将使用CockroachDB的语法和参数,但是其他支持SELECT FOR UPDATE 的SQL数据库的语法也是类似的。

想象一下,我们正在使用一个数据库,其中包括以下表kv

一个在该表上使用SELECT FOR UPDATE 的完整事务可能看起来像这样。

BEGIN;
SELECT * FROM kv WHERE k = 1 FOR UPDATE;
UPDATE kv SET v = v + 5 WHERE k = 1;
COMMIT;

通过上面的语句逐行工作。

  • 第一行,BEGIN ,启动了事务。
  • 第二行,SELECT 语句,识别将被影响的行并锁定它们。在这个例子中,它是一个单行:表的第一行。
  • 第三行对有问题的行进行更新。在本例中,在该行的v 列的数值上增加5。
  • 第四行,COMMIT; ,提交事务。

如果我们在我们的示例表中运行这个事务,结果会是这样。

不过,这里重要的是,SELECT … FOR UPDATE 行(第 2 行)锁定了该事务所更新的行。如果在这个事务(Tx 1)处理过程中,另一个事务(我们称之为Tx 2)进入数据库,试图对同一行进行操作,那么Tx 2将在Tx 1提交后被加入到处理队列中,而不是开始执行,失败,并且不得不重试,因为Tx 1在处理过程中改变了Tx 2正在访问的一个值。

同样,不同的数据库系统允许不同的参数和对SELECT 语句的修改。

例如,一个常见的是SELECT … FOR SHARE ,它在一些数据库系统中提供了一个较弱的行锁定形式。在PostgreSQL中,SELECT … FOR UPDATE 完全锁定了相关的行,而SELECT … FOR SHARE 只对更新和删除的相关行进行锁定。

相比之下,由于CockroachDB总是保证可序列化的隔离,并且没有 "较弱 "的锁定级别,SELECT … FOR SHARE 的功能与SELECT … FOR UPDATE 相同。FOR SHARE 的语法只支持Postgres的兼容性。

另一个常见的参数是NOWAIT ,如果一个事务不能立即锁定一行,它会立即返回一个错误。在SQL语法中,NOWAIT 直接出现在FOR UPDATE 之后,像这样。

SELECT * FROM kv WHERE k = 1 FOR UPDATE NOWAIT;

SKIP LOCKED 也是一些数据库支持的参数,它允许等待中的事务暂时跳过锁定的行,这样对这些行的 "保留 "就不会减缓对非锁定行的事务元素的处理。CockroachDB目前不支持这个,部分原因是它使用多版本并发控制