使用CTE和FOR UPDATE锁定多行

时间:2018-01-24 08:02:41

标签: postgresql postgresql-10

在一个会话中,我尝试锁定“users”表中的多行,并获取用户 $persons = array(0 =>'John', 1 => 'Alan', 2 => 'Ninja'); $fruits = array(0 =>'apple', 1 => 'mango', 2 => 'banana', 3 => 'kiwi'); $str = "[person1] eats [fruit1]"; $str2 = "These [fruit2] belongs to [person3] and his friends"; function replaceFromArr($str) { global $persons, $fruits; $matches = []; preg_match_all("/\[([^\]]*)\]/", $str, $matches); //var_dump($matches[1]); foreach($matches[1] as $matched) { // echo "$matched <br>"; $substr = substr($matched,0,6); if($substr == "person") { // extract index and fetch from persons array $index = substr($matched,6); $replace = $persons[$index-1]; $str = str_replace("[$matched]",$replace,$str); } else { // extract index and fetch from fruits array $index = substr($matched,5); $replace = $fruits[$index-1]; $str = str_replace("[$matched]",$replace,$str); } } return $str; } $newStr = replaceFromArr($str); echo "--- $newStr ---<br>"; $newStr = replaceFromArr($str2); echo "--- $newStr ---<br>"; 的“status”列。

WHERE id = 2

当此事务正在运行时,我在另一个会话中运行此查询:

do $$
declare user_status int;
begin
    WITH t(id, status) AS(
        SELECT id, status FROM users WHERE id in( 2,4,7,6) order by id FOR UPDATE
    )
    SELECT status FROM t WHERE id = 2 INTO user_status;

    -- just run transaction for a while
    FOR i in 1..2000000000 loop
    end loop;

end;
$$ language plpgsql

我预计行UPDATE users SET some_col = some_col WHERE id = 6; 应该被第一个事务锁定,但事实并非如此,因为第二个会话中的WHERE id = 6立即运行(不等待第一个会话中的结束事务)。

我被误解了什么?

P.S。

如果在第一次交易中,而不是使用CTE:

UPDATE

然后锁定按预期工作。

1 个答案:

答案 0 :(得分:1)

我认为这是由于PL / pgSQL的$followedids = rtrim($followedids, ','); 在幕后工作的方式。

docs中提到了明显的根本原因:

  

如果在游标的查询中使用了锁定子句,则实际只有行   光标取出或踩过将被锁定。

...并且此效果将流向查询中的任何CTE,其中只有在外部查询需要行时才会获取(因此锁定)行。

这很容易证明。首先,一些设置:

SELECT INTO

此查询将按预期锁定所有行:

CREATE TABLE t (x INT);
INSERT INTO t VALUES (1),(2),(3);

但是,这会产生您观察到的行为,仅锁定第一条记录:

WITH cte AS (SELECT x FROM t WHERE x IN (1,2,3) FOR UPDATE)
SELECT x FROM cte WHERE x = 1

我的猜测是PL / pgSQL中的DECLARE c CURSOR FOR WITH cte AS (SELECT x FROM t WHERE x IN (1,2,3) FOR UPDATE) SELECT x FROM cte WHERE x = 1; FETCH NEXT FROM c; 以相同的方式运行;如docs中所述,如果查询返回多行,则第一行被分配给目标变量,其余的被忽略(因此不需要它来获取多个记录)。

但是, 锁定SELECT INTO中的所有行:

t

请注意SELECT INTO STRICT的使用。如果查询返回多行,DO $$ DECLARE i INT; BEGIN WITH cte AS (SELECT x FROM t WHERE x IN (1,2,3) FOR UPDATE) SELECT x FROM cte WHERE x = 1 INTO STRICT i; END $$ LANGUAGE plpgsql; 关键字将导致抛出错误。但是为了发现第二行被返回,PL / pgSQL必须尝试获取它,这会导致CTE查询获取剩余的记录(在过程中将它们锁定)。

所有这些在PL / pgSQL函数中大部分时间都不是问题,因为您通常从STRICT查询中获取所有记录,并将它们传递给对谁实际更新它们。另一方面,如果您想要查询的副作用但对其输出不感兴趣,那么(如您所见)您可以使用FOR UPDATE,它将运行查询以完成,但会丢弃结果。

在极少数情况下,您需要(即锁定)所有行,而只返回其中的一部分,您可能需要编写自己的loop