我写了一个函数来为一个简单的博客引擎创建帖子:
CREATE FUNCTION CreatePost(VARCHAR, TEXT, VARCHAR[])
RETURNS INTEGER AS $$
DECLARE
InsertedPostId INTEGER;
TagName VARCHAR;
BEGIN
INSERT INTO Posts (Title, Body)
VALUES ($1, $2)
RETURNING Id INTO InsertedPostId;
FOREACH TagName IN ARRAY $3 LOOP
DECLARE
InsertedTagId INTEGER;
BEGIN
-- I am concerned about this part.
BEGIN
INSERT INTO Tags (Name)
VALUES (TagName)
RETURNING Id INTO InsertedTagId;
EXCEPTION WHEN UNIQUE_VIOLATION THEN
SELECT INTO InsertedTagId Id
FROM Tags
WHERE Name = TagName
FETCH FIRST ROW ONLY;
END;
INSERT INTO Taggings (PostId, TagId)
VALUES (InsertedPostId, InsertedTagId);
END;
END LOOP;
RETURN InsertedPostId;
END;
$$ LANGUAGE 'plpgsql';
当多个用户同时删除标签并创建帖子时,是否容易出现竞争状况?
具体来说,交易(以及功能)是否可以防止此类竞争情况的发生?
我正在使用PostgreSQL 9.2.3。
它的经常性问题 _ SELECT
或INSERT
下可能并发写入负载,涉及(但不同于)UPSERT
(这是 INSERT
或UPDATE
_)。
使用新的UPSERT实施INSERT ... ON CONFLICT .. DO UPDATE
,我们可以大大简化。PL / pgSQL函数到INSERT
或SELECT
一个 _ 单个_ 行(标记):
CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int) AS
$func$
BEGIN
SELECT tag_id -- only if row existed before
FROM tag
WHERE tag = _tag
INTO _tag_id;
IF NOT FOUND THEN
INSERT INTO tag AS t (tag)
VALUES (_tag)
ON CONFLICT (tag) DO NOTHING
RETURNING t.tag_id
INTO _tag_id;
END IF;
END
$func$ LANGUAGE plpgsql;
比赛条件仍然有一个很小的窗口。为了 _ 确保_ 您获得ID,请执行以下操作:
CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int) AS
$func$
BEGIN
LOOP
SELECT tag_id
FROM tag
WHERE tag = _tag
INTO _tag_id;
EXIT WHEN FOUND;
INSERT INTO tag AS t (tag)
VALUES (_tag)
ON CONFLICT (tag) DO NOTHING
RETURNING t.tag_id
INTO _tag_id;
EXIT WHEN FOUND;
END LOOP;
END
$func$ LANGUAGE plpgsql;
这一直循环直到成功INSERT
或失败为止SELECT
。称呼:
SELECT f_tag_id('possibly_new_tag');
如果 同一事务中的 后续命令依赖于该行的存在,并且实际上其他事务可能同时更新或删除该行,则可以使用来锁定SELECT
语句中的现有行FOR SHARE
。
如果改为插入该行,则该行将一直锁定到事务结束为止。
如果大多数时间都插入了新行,则从开始开始INSERT
使其更快。
我以前也曾建议过此SQL函数:
CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int) AS
$func$
WITH ins AS (
INSERT INTO tag AS t (tag)
VALUES (_tag)
ON CONFLICT (tag) DO NOTHING
RETURNING t.tag_id
)
SELECT tag_id FROM ins
UNION ALL
SELECT tag_id FROM tag WHERE tag = _tag
LIMIT 1
$func$ LANGUAGE sql;
这并不是完全错误,但是它无法解决漏洞,就像@FunctorSalad在他的补充答案中得出的那样。如果并发事务尝试同时执行相同的操作,则该函数可能会得出空结果。具有CTE的查询中的所有语句实际上都是在同一时间执行的。手册:
所有语句都使用相同的快照执行
如果并发事务稍早插入了相同的新标记,但尚未提交:
等待并发事务完成后,UPSERT部分变为空。(如果并发事务应回滚,它仍会插入新标签并返回新ID。)
SELECT部分也空了,因为它基于相同的快照,在该快照中,(尚未提交的)并发事务中的新标记不可见。
我们 什么也得不到 。不符合预期。这与朴素的逻辑(我被困在那里)是违反直觉的,但这就是Postgres的MVCC模型的工作原理-必须起作用。
因此,如果多个事务可以尝试同时插入同一标签,请不要使用此选项。 或 循环播放,直到您真正得到一行。在常见的工作负载中几乎不会触发该循环。
给定此表(略有简化):
CREATE table tag (
tag_id serial PRIMARY KEY
, tag text UNIQUE
);
…一种 实际上是100%安全的 功能,可以插入新标签/选择现有标签,看起来像这样。
为什么不100%?请参考手册中有关相关UPSERT
示例的注释:
CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT tag_id int) AS
$func$
BEGIN
LOOP
BEGIN
WITH sel AS (SELECT t.tag_id FROM tag t WHERE t.tag = _tag FOR SHARE)
, ins AS (INSERT INTO tag(tag)
SELECT _tag
WHERE NOT EXISTS (SELECT 1 FROM sel) -- only if not found
RETURNING tag.tag_id) -- qualified so no conflict with param
SELECT sel.tag_id FROM sel
UNION ALL
SELECT ins.tag_id FROM ins
INTO tag_id;
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- insert in concurrent session?
RAISE NOTICE 'It actually happened!'; -- hardly ever happens
END;
EXIT WHEN tag_id IS NOT NULL; -- else keep looping
END LOOP;
END
$func$ LANGUAGE plpgsql;
尝试第SELECT
一个 。这样,您可以避免99.99%的时间处理异常 昂贵的 异常。
使用CTE可以最大程度地减少竞争条件下的(已经很小的)时隙。
一个查询中 的SELECT
和之间的时间窗口非常小。如果您没有繁重的并发负载,或者您可以每年遇到一次异常,则可以忽略这种情况,而使用SQL语句,这会更快。INSERT
__
不需要FETCH FIRST ROW ONLY
(= LIMIT 1
)。标签名称显然是UNIQUE
。
FOR SHARE
如果您通常没有并发DELETE
或不在UPDATE
表中,请在我的示例中删除tag
。花费一点点性能。
切勿引用语言名称:
‘plpgsql’
。plpgsql
是一个 标识符 。报价可能会引起问题,并且仅允许向后兼容。
请勿使用非描述性的列名,例如id
或name
。当联接几个表时( 这是 在关系数据库中 所做的事情 ),您最终会使用多个相同的名称,并且必须使用别名。
使用此功能,您可以在很大程度上简化FOREACH LOOP
为:
...
FOREACH TagName IN ARRAY $3
LOOP
INSERT INTO taggings (PostId, TagId)
VALUES (InsertedPostId, f_tag_id(TagName));
END LOOP;
...
不过,作为带有以下命令的单个SQL语句,速度更快unnest()
:
INSERT INTO taggings (PostId, TagId)
SELECT InsertedPostId, f_tag_id(tag)
FROM unnest($3) tag;
替换整个循环。
此变体基于UNION ALL
withLIMIT
子句的行为:一旦找到足够的行,就永远不会执行其余的行
在此基础上,我们可以将外包给INSERT
一个单独的函数。只有在那里,我们需要异常处理。和第一个解决方案一样安全。
CREATE OR REPLACE FUNCTION f_insert_tag(_tag text, OUT tag_id int)
RETURNS int AS
$func$
BEGIN
INSERT INTO tag(tag) VALUES (_tag) RETURNING tag.tag_id INTO tag_id;
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- catch exception, NULL is returned
END
$func$ LANGUAGE plpgsql;
主要功能中使用的是:
CREATE OR REPLACE FUNCTION f_tag_id(_tag text, OUT _tag_id int) AS
$func$
BEGIN
LOOP
SELECT tag_id FROM tag WHERE tag = _tag
UNION ALL
SELECT f_insert_tag(_tag) -- only executed if tag not found
LIMIT 1 -- not strictly necessary, just to be clear
INTO _tag_id;
EXIT WHEN _tag_id IS NOT NULL; -- else keep looping
END LOOP;
END
$func$ LANGUAGE plpgsql;
如果大多数调用只需要它SELECT
,这会便宜一些,因为很少输入INSERT
包含EXCEPTION
子句的更昂贵的块。查询也更简单。
FOR SHARE
在这里是不可能的(UNION
查询中不允许)。
LIMIT 1
不需要(在第9.4页中进行了测试)。Postgres派生LIMIT 1
自INTO _tag_id
并执行直到找到第一行。
主要内容:锁住共享资源有并发,就有资源竞争,如果两个或者多个 goroutine 在没有相互同步的情况下,访问某个共享的资源,比如同时对该资源进行读写时,就会处于相互竞争的状态,这就是并发中的资源竞争。 并发本身并不复杂,但是因为有了资源竞争的问题,就使得我们开发出好的并发程序变得复杂起来,因为会引起很多莫名其妙的问题。 下面的代码中就会出现竞争状态: 这是一个资源竞争的例子,大家可以将程序多运行几次,会发现结果可能是
问题内容: 我正在使用具有connect-redis的nodejs来存储会话数据。 我将用户数据保存在会话中,并在会话生存期内使用它。 我注意到,在两个更改会话数据的请求之间可能存在竞争状态。 我尝试使用redis-lock锁定会话,但是对我来说有点麻烦。 我不想锁定整个会话,而是只锁定特定的会话变量。 我发现这是不可能的,我考虑了解决的方向: 停止使用会话对象存储用户数据,并将变量直接保存在re
问题内容: 我有以下T-SQL代码: 我正在使用带有注释的“锁定”黑客来获取排他锁定。 注意:使用TABLOCKX或UPDLOCK提示将不起作用,因为我通过拆分语句并在中间添加WAITFOR命令进行测试而破坏了ATOMIC- ity。我不要这样的东西: 在同时运行两个会话(带锁表)之后,这是正确的结果 那是运行带有注释的代码的不正确结果 没有这样的黑客,有没有更好的方法来获取交易范围内的排他锁?
9.1. 竞争条件 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法
我正在创建套接字类,但我想使我的连接功能是动态的,可以连接到地址(ipv4或ipv6),使用交换机进行IPv测试并连接到支持的IPv,只是想问一下我是否是对的,或者有没有一个简单的方法使它成为ipv4或ipv6?
我使用的是Postgres 9.1。我想知道在同一个事务中使用多个SELECT FOR UPDATES是否可能导致竞争情况。 2个并发事务: 事务1:选择表1上的更新--成功获取锁 事务2:选择表2上的更新--成功获取锁 事务2:选择表1上的更新--等待从事务1释放锁 事务 1:选择更新表 2 -- 等待事务 2 中的锁定释放 在这种情况下会发生什么?其中一个等待的事务最终会超时吗?如果是,是否有