如何使PostgreSQL函数成为原子?

时间:2014-09-27 15:50:50

标签: sql postgresql stored-procedures plpgsql psql

假设我有一些PostgreSQL函数,如下所示:

CREATE FUNCTION insertSth() RETURNS void AS $$
BEGIN
    INSERT INTO ...;
END;

CREATE FUNCTION removeSthAfterSelect() RETURNS TABLE(...) AS $$
BEGIN
     SELECT id INTO some_id ...;
     RETURN QUERY SELECT * FROM ...;
     DELETE FROM ... WHERE id = some_id;
END;

CREATE FUNCTION justDeleteSth() RETURNS void AS $$
BEGIN
     DELETE FROM ...;
END;

CREATE FUNCTION justSelectSth() RETURNS TABLE(...) AS $$
BEGIN
     RETURN SELECT * FROM ...;
END;

根据我的理解,PostgresSQL函数insertSthjustDeleteSthjustSelectSth将以原子方式执行(?)。因此,他们的并行执行不会搞砸任何事情。

但对于removeSthAfterSelect,如果存在并行执行,则可能SELECT id INTO some_id ..找到某些内容,然后同时另一个事务调用justDeleteSth并删除id = someId行,当交易继续时,它不会删除任何内容:DELETE FROM ... WHERE id = some_id;意味着它会让事情变得混乱。

是这样的吗? 有没有办法避免这个问题?例如。通过说removeSthAfterSelect应该原子地执行?

2 个答案:

答案 0 :(得分:8)

一个事务具有原子 commit 的属性,即整个事务保证生效,或者没有一个事件生效。

这并不意味着交易无法互动。特别是,在READ COMMITTED模式下,通过另一个事务中途提交的事务可以具有可见效果。即使没有它,同时异常也是可能的和正常的。请参阅the PostgreSQL chapter on concurrency control,尤其是transaction isolation部分。 与独立语句相比,函数语句对并发性问题不再具有免疫力。

即使在单个语句中,也可能存在并发问题。陈述不是神奇的原子。人们通常认为,如果他们可以使用CTE,子查询等将所有内容打包到单个查询中,那么它就会神奇地免受并发问题的影响。事实并非如此。

没有函数标签可以说“以原子方式执行”,因为您正在寻找的概念在DBMS中不存在。你得到的最接近的是LOCK TABLE ... IN ACCESS EXCLUSIVE函数使用的所有表,这样其他任何表都无法触及它们。如果你可以有效地推断并发和事务隔离,那通常是相当过分和不必要的。

很难更具体,因为你正在使用一个非常概括的例子,遗漏了所有细节。例如,如果您尝试两次删除行,为什么重要?

您应该学习的一些概念:

  • 快照
  • READ COMMITTED vs SERIALIZABLE事务隔离
  • 行和表级别锁定,包括隐式(例如由DML采用的那些)和显式(例如SELECT ... FOR UPDATE
  • 交易可见性
  • DML语句完成等待锁定后的谓词重新检查

作为并发操作的一个示例,请查看upsert problem


  

但是对于removeSthAfterSelect,如果存在并行执行,则可能是SELECT id INTO some_id ..找到了什么,然后另一个事务同时调用justDeleteSth并删除id = someId的行,所以当事务继续时它不会删除这里有什么:DELETE FROM ... WHERE id = some_id;这意味着它搞砸了。

你说的就好像一个交易停止而另一个交易运行,然后第一个交易继续。通常情况并非如此;事情可以完全同时发生,许多陈述真正同时发生。

限制的主要因素是行级锁定。在这种情况下,存在竞争条件,因为DELETE都试图获取该行的行更新锁。无论哪个获取它将继续并删除该行。另一个DELETE卡在行锁上,直到获胜的事务提交或回滚。如果它回滚,就好像什么都没发生一样,等待的交易继续正常进行。如果获胜事务提交删除,则等待事务看到锁已被释放,并且(在READ COMMITTED模式下)重新检查WHERE子句谓词以确保该行仍然匹配 ,发现它不再存在,并且继续没有错误,因为删除零行不是错误。

在PL / PgSQL中,如果要强制某个语句只影响一行,则可以检查受影响的行数,如果它与预期的受影响行不匹配,则可以RAISE EXCEPTIONINTO STRICT还有SELECT

答案 1 :(得分:0)

通常可以使用锁定来实现所需的“原子”行为

例如:

BEGIN;  -- transaction
SELECT pg_advisory_xact_lock(123);  -- 123 is any big integer

-- do your "atomic" stuff here, other transactions
-- trying to acquire the same (123) lock will be waiting for it to be released

COMMIT;  -- transaction has ended, the locks are released automatically

缺点是这种锁定的块不会并行执行。有关详细信息,请参阅文档https://www.postgresql.org/docs/11/explicit-locking.html