我在多对多插入时遇到了死锁问题,并且在这一点上远远超出了我的范围。
我有一个tweet
每秒接收数千条记录的表。其中一列是 PostgreSQLarray[]::text[]
类型,在数组中具有零对多的 url。它看起来像{www.blah.com, www.blah2.com}
。
我试图从表上的触发器中完成的是在tweet
表中创建一个条目,urls_starting
然后在tweet_x_url_starting
.
旁注:该url_starting
表链接到url_ending
完全解析的 url 路径所在的表。
我面临的问题是死锁,我不知道还能尝试什么。
我继续进行Erwin Brandstetter学习狂欢。(如果你在外面,伙计......谢谢!?)
- 如何在 PostgreSQL 中实现多对多关系?
- 尽管 ON CONFLICT DO NOTHING 导致多行 INSERT 死锁
- Postgres 更新……限制 1
(skip locked help)
我尝试为确定性、稳定的订单和 FOR UPDATE SKIP LOCKED 添加 ORDER BY,但我不确定我是否正确地执行了任何操作。
这是结构。使用 PostgreSQL 10.5。
CREATE TABLE tweet(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
twitter_id text NOT NULL,
created_at timestamp NOT NULL,
content text NOT NULL,
urls text[],
CONSTRAINT tweet_pk PRIMARY KEY (id)
);
CREATE TABLE url_starting(
id integer NOT NULL GENERATED BY DEFAULT AS IDENTITY,
url text NOT NULL,
CONSTRAINT url_starting_pk PRIMARY KEY (id),
CONSTRAINT url_starting_ak_1 UNIQUE (url)
);
CREATE TABLE tweet_x_url_starting(
id_tweet integer NOT NULL,
id_url_starting integer NOT NULL,
CONSTRAINT tweet_x_url_starting_pk PRIMARY KEY (id_tweet,id_url_starting)
ALTER TABLE tweet_x_url_starting ADD CONSTRAINT tweet_fk FOREIGN KEY (id_tweet)
REFERENCES tweet (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE tweet_x_url_starting ADD CONSTRAINT url_starting_fk FOREIGN KEY (id_url_starting)
REFERENCES url_starting (id) MATCH FULL
ON DELETE CASCADE ON UPDATE CASCADE;
这是tweet
表触发器。
CREATE TRIGGER create_tweet_relationships
AFTER INSERT OR UPDATE
ON tweet
FOR EACH ROW
EXECUTE PROCEDURE create_tweet_relationships();
最后,功能。
CREATE FUNCTION create_tweet_relationships ()
RETURNS trigger
LANGUAGE plpgsql
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 1
AS $$
BEGIN
IF (NEW.urls IS NOT NULL) AND cardinality(NEW.urls::TEXT[]) > 0 THEN
WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO tweet_x_url_starting (id_tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING;
END IF;
RETURN NULL;
END
$$;
我盲目地将我读到的东西扔到函数中,但没有成功。
错误看起来像这样。
deadlock detected
DETAIL: Process 11281 waits for ShareLock on transaction 1317; blocked by process 11278.
Process 11278 waits for ShareLock on transaction 1316; blocked by process 11281.
HINT: See server log for query details.
CONTEXT: while inserting index tuple (494,33) in relation "url_starting"
SQL statement "WITH tmp_url AS (
INSERT INTO url_starting (url)
SELECT UNNEST(NEW.urls)
ORDER BY 1
ON CONFLICT (url) DO UPDATE
SET url = EXCLUDED.url
RETURNING id
)
INSERT INTO tweet_x_url_starting (id_tweet, id_url_starting)
SELECT NEW.id, id
FROM tmp_url
ORDER BY 1, 2
FOR UPDATE SKIP LOCKED
ON CONFLICT DO NOTHING"
PL/pgSQL function create_tweet_relationships() line 12 at SQL statement
Error causing transaction rollback (deadlocks, serialization failures, etc).
我怎样才能停止死锁?谢谢!?
这最终归结为两件事。
如果插入的数据在插入前没有排序,并发写入最终会死锁。在我的触发器函数中,所有插入都已排序,但无法对同时添加的所有 url 进行排序。解决此问题的唯一方法是备份一个级别并使用整批推文进行插入/排序,从而一次访问所有 URL。
更多在这里。?如何在 PostgreSQL 中使用 RETURNING 和 ON CONFLICT?
这样做有很大的不同,但并没有完全解决问题。?
该
ON CONFLICT
子句可以防止重复键错误,但不能防止并发事务尝试输入相同的键。更多在这里。?尽管 ON CONFLICT DO NOTHING 导致多行 INSERT 死锁
如我的问题中的错误消息所示,系统元组索引
ctid
在执行ON CONFLICT (column) DO UPDATE
. 幸运的是,我不需要更新任何数据,因此不需要DO UPDATE
我的部分查询。修复这个100% 停止了死锁!?
这里 ? 是使用 python 发送的最终查询,带有 ) 中的
execute_values()
函数psycopg2
。这可能需要仔细检查,了解完整情况以及我现在无法花费的更多时间。或者,也许我遗漏了一些明显的东西。无论这里可能出现什么问题,有些事情很突出:
完全删除
FOR UPDATE SKIP LOCKED
。在哪里使用它是没有意义的。当从 CTE 中选择时,它已经拥有排他锁的行,这是没有意义的。在查询的这个阶段跳过任何行也是没有意义的。COST 1
具有误导性。默认是COST 100
,您的触发功能更多地在 COST 5000 的范围内。保留默认值或将其设置得更高。可能与死锁无关。与使用
AFTER
单个查询(具有多个数据修改 CTE)重写整个工作流相比,触发器可能更容易出现死锁。我在黑暗中开枪:死锁是由 FK 约束试图采取 ShareLock 造成的,
url_starting
而并发事务尝试在自己采取类似的 ShareLock 后修改同一行,反之亦然。tweet_x_url_starting
如果您负担得起,一个快速而肮脏的解决方案可能是放弃 FK 约束。您至少可以尝试验证它是问题的一部分。如果你想继续你的学习狂潮 - 这里还有一个似乎相关的: