如何使 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 函数 insertSth
、justDeleteSth
和 justSelectSth
将自动执行(?)。所以它们的并行执行不会搞砸任何事情。
但是对于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 COMMITTED
与 SERIALIZABLE
事务隔离
行和表级别的锁,包括隐式(例如 DML 采用的)和显式(例如 SELECT ... FOR UPDATE
)
交易可见性
在 DML 语句完成等待锁定后重新检查谓词
作为一个实际的并发示例,请查看upsert problem。
但是对于 removeSthAfterSelect,如果有并行执行,可能是 SELECT id INTO some_id .. 找到了一些东西,然后另一个事务同时调用 justDeleteSth 并删除 id = someId 的行,所以当事务继续时它不会删除此处的任何内容:删除... WHERE id = some_id;这意味着它把事情搞砸了。
您说的好像一个事务停止,另一个事务运行,然后第一个事务继续。通常情况并非如此。事情可以完全同时运行,许多语句真正同时发生。
主要的限制是行级锁定。在这种情况下,存在竞争条件,因为DELETE
s 都尝试获取行的行更新锁。无论哪个得到它将继续并删除该行。另一个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 函数原子化?的主要内容,如果未能解决你的问题,请参考以下文章
PostgreSQL如何对结果进行分组以使所有行都必须为真?
如何使用 JPQL、Spring Data Repositories 和 Hibernate 为 TimescaleDB `time_bucket` 函数参数化 Postgresql 间隔