如何使 PostgreSQL 函数原子化?

Posted

技术标签:

【中文标题】如何使 PostgreSQL 函数原子化?【英文标题】:How to make PostgreSQL functions atomic? 【发布时间】:2014-09-27 15:50:50 【问题描述】:

假设我有一些 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应该被原子执行?

【问题讨论】:

【参考方案1】:

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

这并不意味着事务不能交互。特别是,在READ COMMITTED 模式下,一个事务在中途提交,而另一个事务可能会产生可见的影响。即使没有这个,同时异常也是可能的并且是正常的。请参阅the PostgreSQL chapter on concurrency control,尤其是transaction isolation 部分。 函数中的语句并不比独立语句更不受并发问题的影响。

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

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

很难更具体,因为您使用的是一个非常笼统的示例,而忽略了所有细节。例如,如果您尝试删除该行两次,这有什么关系?

你应该学习的几个概念:

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

作为一个实际的并发示例,请查看upsert problem。


但是对于 removeSthAfterSelect,如果有并行执行,可能是 SELECT id INTO some_id .. 找到了一些东西,然后另一个事务同时调用 justDeleteSth 并删除 id = someId 的行,所以当事务继续时它不会删除此处的任何内容:删除... WHERE id = some_id;这意味着它把事情搞砸了。

您说的好像一个事务停止,另一个事务运行,然后第一个事务继续。通常情况并非如此。事情可以完全同时运行,许多语句真正同时发生。

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

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

【讨论】:

感谢您的回答。在阅读了事务隔离之后,我又提出了一个问题,可以在这里找到:***.com/questions/26195339/…。如果你能看看我会很高兴:)【参考方案2】:

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

例如:

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

【讨论】:

以上是关于如何使 PostgreSQL 函数原子化?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle SQL - 使事务原子化

PostgreSQL如何对结果进行分组以使所有行都必须为真?

如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔

HarmonyOS-5分钟教会你原子化服务

SQL优化 MVCC PostgreSQL实现事务和多版本并发控制的精华

spark append 如何保证原子性