Sto implementando un semplice lettore RSS basato sul web usando python (non proprio rilevante) e Postgresql (9.2 se rilevante). Lo schema del database è la seguente (basato sul formato RSS):Inserisci riga se non esiste conduce a condizioni di gara?
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
);
Quando creo un nuovo canale (e anche interrogare per info feed aggiornato) Chiedo il feed, inserire i propri dati alla tabella feed_channel, seleziona l'ID appena inserito - o esistente per evitare duplicati - e quindi aggiungere i dati del feed alla tabella feed_content. Uno scenario tipico potrebbe essere:
- Query l'URL del feed, header feed afferrare e tutti i contenuti attuali
- Inserire le header feed in feed_channel se non esiste ... se esiste già, afferrare l'ID esistente
- Per ogni voce di feed, inserire nella tabella feed_content con un riferimento all'ID canale memorizzato
Questo è un problema standard "Inserisci se non esiste già, ma restituisce ID pertinente". Per risolvere questo ho implementato la stored procedure seguente:
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;
Questo viene poi chiamato come "selezionare channel_insert (link, titolo);" dalla mia applicazione per inserire se non esiste già e quindi restituire l'ID della riga pertinente indipendentemente dal fatto che sia stato inserito o appena trovato (passaggio 2 nella lista sopra).
Questo funziona benissimo!
Tuttavia, recentemente ho iniziato a chiedermi cosa succederebbe se questa procedura fosse eseguita due volte contemporaneamente con gli stessi argomenti. Consente di assumere la seguente:
- utente 1 tenta di aggiungere un nuovo canale e quindi eseguire channel_insert
- pochi ms dopo, User 2 tentativi per aggiungere sullo stesso canale e anche la tecnologia execute channel_insert
- utente 1 di controllo per le righe esistenti sono completate, ma prima che l'inserimento sia completo, il controllo dell'Utente 2 viene completato e dice che non ci sono righe esistenti.
Questa è una potenziale condizione di competizione in PostgreSQL? Qual è il modo migliore per risolvere questo problema per evitare tali scenari? È possibile rendere atomicamente l'intera procedura memorizzata, cioè che può essere eseguita una sola volta nello stesso momento?
Un'opzione che ho provato era quella di rendere i campi Unici e poi tentare di inserire prima, e se l'eccezione, invece, selezionare l'esistente ... Questo funzionava, tuttavia, il campo SERIAL avrebbe incrementato per ogni tentativo, lasciando un sacco di lacune nella sequenza. Non so se questo sarebbe un problema a lungo termine (probabilmente no), ma piuttosto fastidioso. Forse questa è la soluzione preferita?
Grazie per qualsiasi feedback. Questo livello di magia di PostgreSQL è al di fuori di me, quindi qualsiasi feedback sarebbe apprezzato.
Non importa quello che fai, stai attento a normalizzare il formato link in modo che non avete problemi di casi ('' Www.Example.Com' e www.example .com'), l'ordine dei parametri issus ('? a = b & c = d' e'? c = d & a = b'), ecc. –
Un loop di funzione plpgsql in caso di violazione di una chiave duplicata può gestire la condizione della competizione sul lato server e al livello di isolamento predefinito, che è * sicuro * tipicamente * più economico *: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to- condizioni di gara/15950324 # 15950324 –