Я использую простой RSS-ридер на основе Интернета, используя python (не очень релевантный) и Postgresql (если это необходимо 9.2). Схема базы данных выглядит следующим образом (на основе формата 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, выбирает вновь вставленный идентификатор или существующий, чтобы избежать дублирования, - а затем добавляйте данные фида к таблицу feed_content. Типичным сценарием будет:
- Запросить URL-адрес канала подачи, заголовки каналов захвата и весь текущий контент
- Вставьте заголовки каналов в канал feed_channel, если он не существует... если он уже существует, возьмите существующий идентификатор
- Для каждого элемента подачи вставьте в таблицу feed_content ссылку на сохраненный идентификатор канала
Это стандартная "вставка, если она еще не существует, но возвращает соответствующий идентификатор". Чтобы решить эту проблему, я выполнил следующую хранимую процедуру:
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;
Затем это называется "select channel_insert (ссылка, название)"; из моего приложения вставить, если оно еще не существует, и затем вернуть идентификатор соответствующей строки независимо от того, была ли она вставлена или просто найдена (шаг 2 в списке выше).
Это отлично работает!
Однако я недавно начал задаваться вопросом, что произойдет, если эта процедура будет выполнена дважды в то же время с теми же аргументами. Предположим следующее:
- Пользователь 1 пытается добавить новый канал и тем самым выполнить channel_insert
- Через несколько минут пользователь 2 пытается добавить тот же канал, а также выполнить channel_insert
- Пользователь 1 проверяет наличие существующих строк, но до завершения вставки проверка User 2 завершается и говорит, что нет существующих строк.
Будет ли это потенциальное состояние гонки в PostgreSQL? Каков наилучший способ решить эту проблему, чтобы избежать таких сценариев? Возможно ли сделать всю хранимую процедуру атомарно, т.е. Что ее можно выполнить только один раз в одно и то же время?
Один из вариантов, который я пытался сделать, - это сделать поля Unique, а затем попытаться вставить сначала, а если исключение, выберите существующее вместо этого... Это сработало, однако, поле SERIAL увеличивалось бы для каждой попытки, оставляя много пробелов в последовательности. Я не знаю, будет ли это проблемой в долгосрочной перспективе (возможно, нет), но вроде бы раздражает. Возможно, это предпочтительное решение?
Спасибо за любую обратную связь. Этот уровень магии PostgreSQL превосходит меня, поэтому любая обратная связь будет оценена.