DETAIL: Key (proname, proargtypes, pronamespace)=(rand, , 2200) already exists.: CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
这到底怎么可能?CREATE OR REPLACE
已经存在怎么会失败?这是在一个 docker 容器中,完全原始的环境,这是唯一的代码运行。是的,它可能会反复运行,但这OR REPLACE
就是存在的原因。我不明白这一点。这是完整的命令:
if (!$connection->query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
$connection->query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
\'SELECT random();\'
LANGUAGE \'sql\'');
}
中的
OR REPLACE
子句CREATE FUNCTION
并不是为了无缝并行执行,它是为了避免在我们只想更新主体时删除函数。从文档:现在,如果多个会话
CREATE OR REPLACE FUNCTION
并行发出相同的问题,则确实存在潜在的竞争条件,该条件依赖于 上的唯一索引来处理(proname, proargtypes, pronamespace)
,并作为错误返回给调用者。问题中提到的错误发生在例如:
session #1 启动
CREATE OR REPLACE
并发现该函数不存在,因此它创建它,插入pg_proc
并锁定索引中的相应(proname, proargtypes, pronamespace)
条目。会话#2 开始
CREATE OR REPLACE
,发现函数不存在(因为#1 尚未提交)并被置于等待索引锁。会话 #1 提交。
会话 #2 尝试插入并失败。
请注意,不使用显式 BEGIN/COMMIT 不会改变竞争条件:BEGIN/COMMIT 对只是隐含在每个 SQL 语句周围。
要让这个并行序列无缝工作,
OR REPLACE
需要CREATE FUNCTION
什么ON CONFLICT
是 toINSERT
,但事实并非如此。为了处理可能并行运行的冲突或相同的 DDL,您可能应该在序列的开头使用显式锁,以将其作为一个整体放在关键部分中。建议锁可能用于此。或者您需要使用围绕单个语句的保存点来实施“重试失败”策略。