views:

158

answers:

2

I have postgresql (in perlu) function getTravelTime(integer, timestamp), which tries to select data for specified ID and timestamp. If there are no data or if data is old, it downloads them from external server (downloading time ~300ms).

Multiple process use this database and this function. There is an error when two process do not find data and download them and try to do an insert to travel_time table (id and timestamp pair have to be unique). I thought about locks. Locking whole table would block all processes and allow only one to proceed. I need to lock only on id and timestamp. pg_advisory_lock seems to lock only in "current session". But my processes uses their own sessions.

I tried to write my own lock/unlock functions. Am I doing it right? I use active waiting, how can I omit this? Maybe there is a way to use pg_advisory_lock() as global lock?

My code:

CREATE TABLE travel_time_locks (
    id_key integer NOT NULL,
    time_key timestamp without time zone NOT NULL,
    UNIQUE (id_key, time_key) 
);

------------
-- Function: mylock(integer, timestamp)
DROP FUNCTION IF EXISTS mylock(integer, timestamp) CASCADE;
-- Usage: SELECT mylock(1, '2010-03-28T19:45');
-- function tries to do a global lock similar to pg_advisory_lock(key, key)
CREATE OR REPLACE FUNCTION mylock(id_input integer, time_input timestamp)
  RETURNS void AS
$BODY$
DECLARE
    rows int;
BEGIN
    LOOP

        BEGIN
            -- active waiting here !!!! :(
            INSERT INTO travel_time_locks (id_key, time_key) VALUES (id_input, time_input);
        EXCEPTION WHEN unique_violation THEN
            CONTINUE;
        END;
        EXIT;
    END LOOP;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE
    COST 1;

------------
-- Function: myunlock(integer, timestamp)
DROP FUNCTION IF EXISTS myunlock(integer, timestamp) CASCADE;
-- Usage: SELECT myunlock(1, '2010-03-28T19:45');
-- function tries to do a global unlock similar to pg_advisory_unlock(key, key)
CREATE OR REPLACE FUNCTION myunlock(id_input integer, time_input timestamp)
  RETURNS integer AS
$BODY$
DECLARE
BEGIN
    DELETE FROM ONLY travel_time_locks WHERE id_key=id_input AND time_key=time_input;
    RETURN 1;
END;
$BODY$ LANGUAGE 'plpgsql' VOLATILE
    COST 1;
+1  A: 

Hello

use pg_advisory_lock(), please. It is exactly it what you need.

Pavel

Pavel Stehule
In phppgadmin I can perform :SELECT pg_advisory_lock(5);SELECT pg_advisory_lock(5);SELECT 5;I expect to be blocked at second query... But instead I get "5" after no more than 1ms... Why?
rafalmag
Ok. Now I know why. SQL window in phpPgAdmin is not a transaction. Locks are auto unlocked when nothing is "using" them.Hint: better test locks in 'psql'
rafalmag
A: 

After further testing I found that "current session" means whole instance of PostgreSQL backend, not just a single connection.

My solution is :PERFORM pg_advisory_lock(id, extract(epoch from my_time)::int ); (+ unlock) inside my getTravelTime function.

rafalmag