我正在使用 python(不是很相关)和 Postgresql(如果相关,则为 9.2)实现一个简单的基于 Web 的 RSS 阅读器。数据库架构如下(基于RSS格式):
CREATE TABLE feed_channel
(
id SERIAL PRIMARY KEY,
name TEXT,
link TEXT NOT NULL,
title TEXT
);
CREATE TABLE feed_content
(
id SERIAL PRIMARY KEY,
channel INTEGER REFERENCES feed_channel(id) ON DELETE CASCADE ON UPDATE CASCADE,
guid TEXT UNIQUE NOT NULL,
title TEXT,
link TEXT,
description TEXT,
pubdate TIMESTAMP
);
当我创建一个新频道(并查询更新的提要信息)时,我请求提要,将其数据插入 feed_channel 表,选择新插入的 ID - 或现有 ID 以避免重复 - 然后将提要数据添加到 feed_content 表. 一个典型的场景是:
- 查询提要 url,抓取提要标题和所有当前内容
- 如果不存在,则将提要标题插入 feed_channel ... 如果已存在,则获取现有 ID
- 对于每个提要项目,插入 feed_content 表并引用存储的频道 ID
这是一个标准的“如果不存在则插入,但返回相关 ID”问题。为了解决这个问题,我实现了以下存储过程:
CREATE OR REPLACE FUNCTION channel_insert(
p_link feed_channel.link%TYPE,
p_title feed_channel.title%TYPE
) RETURNS feed_channel.id%TYPE AS $$
DECLARE
v_id feed_channel.id%TYPE;
BEGIN
SELECT id
INTO v_id
FROM feed_channel
WHERE link=p_link AND title=p_title
LIMIT 1;
IF v_id IS NULL THEN
INSERT INTO feed_channel(name,link,title)
VALUES (DEFAULT,p_link,p_title)
RETURNING id INTO v_id;
END IF;
RETURN v_id;
END;
$$ LANGUAGE plpgsql;
然后将其称为“选择通道插入(链接,标题);” 如果不存在,则从我的应用程序中插入,然后返回相关行的 ID,无论它是插入还是刚刚找到(上面列表中的步骤 2)。
这很好用!
然而,我最近开始想知道如果这个过程使用相同的参数同时执行两次会发生什么。让我们假设以下内容:
- 用户 1 尝试添加一个新频道,从而执行 channel_insert
- 几毫秒后,用户 2 尝试添加相同的频道并执行 channel_insert
- 用户 1 对现有行的检查完成,但在插入完成之前,用户 2 的检查完成并说没有现有行。
这会是 PostgreSQL 中潜在的竞争条件吗?解决此问题以避免此类情况的最佳方法是什么?是否可以使整个存储过程原子化,即它只能同时执行一次?
我尝试的一个选项是使字段唯一,然后尝试首先插入,如果出现异常,请选择现有的...这有效,但是,SERIAL 字段会随着每次尝试而增加,从而在序列中留下很多空白. 我不知道从长远来看这是否会成为问题(可能不是),但有点烦人。也许这是首选的解决方案?
感谢您的任何反馈。这个级别的 PostgreSQL 魔法超出了我的能力范围,所以任何反馈都将不胜感激。