views:

30

answers:

1

I have a trigger that executes a function on table insert or update. It looks like this:

CREATE OR REPLACE FUNCTION func_fk_location_area()
RETURNS "trigger" AS $$
BEGIN
    IF EXISTS (
        -- there was a row valid in area when location started
        SELECT * FROM location
        WHERE NOT EXISTS (
            SELECT * FROM area
             WHERE area.key=location.key
               AND area.id=location.area_id
               AND (  (area.tr_from<=location.tr_from AND area.tr_until>location.tr_from) OR
                      (area.tr_from=location.tr_from AND area.tr_until=location.tr_from)))
    ) OR EXISTS (
        -- there was a row valid in area when location ended
        SELECT * FROM location
        WHERE NOT EXISTS (
            SELECT * FROM area
             WHERE area.key=location.key
               AND area.id=location.area_id
               AND (  (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR
                      (area.tr_from=location.tr_until AND area.tr_until=location.tr_until)))
    )
    THEN
        RAISE EXCEPTION 'FK location_area integrity violation.';
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_fk_area_location AFTER DELETE OR UPDATE ON area
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area();
CREATE TRIGGER trigger_fk_location_area AFTER INSERT OR UPDATE ON location
    FOR EACH ROW EXECUTE PROCEDURE func_fk_location_area();

When I insert a row, it seems to run very slowly. Using explain analyze I determined that this trigger was taking nearly 400ms to complete.

 Result  (cost=0.00..0.03 rows=1 width=0) (actual time=0.026..0.029 rows=1 loops=1)
 Trigger for constraint location_fkey_tr_by: time=0.063 calls=1
 Trigger trigger_fk_location_area: time=361.878 calls=1
 Trigger trigger_update_objects_location: time=355.033 calls=1
 Total runtime: 717.229 ms
(5 rows)

However, if I run the two lots of SQL in the function, they each only take 3 or 4ms to run!

FIRST PART:

mydb=# explain analyze
mydb-#             SELECT * FROM location
mydb-#             WHERE NOT EXISTS (
mydb(#                 SELECT * FROM area
mydb(#                  WHERE area.key=location.key
mydb(#                    AND area.id=location.area_id
mydb(#                    AND (  (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR
mydb(#                           (area.tr_from=location.tr_until AND area.tr_until=location.tr_until)));

 Hash Anti Join  (cost=14.68..146.84 rows=1754 width=126) (actual time=5.512..5.512 rows=0 loops=1)
   Hash Cond: ((location.key = area.key) AND (location.area_id = area.id))
   Join Filter: (((area.tr_from < location.tr_until) AND (area.tr_until >= location.tr_until)) OR ((area.tr_from = location.tr_until) AND (area.tr_until = locat
ion.tr_until)))
   ->  Seq Scan on location  (cost=0.00..79.91 rows=2391 width=126) (actual time=0.005..1.016 rows=2393 loops=1)
   ->  Hash  (cost=8.87..8.87 rows=387 width=37) (actual time=0.497..0.497 rows=387 loops=1)
         ->  Seq Scan on area  (cost=0.00..8.87 rows=387 width=37) (actual time=0.004..0.250 rows=387 loops=1)
 Total runtime: 5.562 ms
(7 rows)

SECOND PART:

mydb=# explain analyze
mydb-#             SELECT * FROM location
mydb-#             WHERE NOT EXISTS (
mydb(#                 SELECT * FROM area
mydb(#                  WHERE area.key=location.key
mydb(#                    AND area.id=location.area_id
mydb(#                    AND (  (area.tr_from<location.tr_until AND area.tr_until>=location.tr_until) OR
mydb(#                           (area.tr_from=location.tr_until AND area.tr_until=location.tr_until)));

 Hash Anti Join  (cost=14.68..146.84 rows=1754 width=126) (actual time=5.666..5.666 rows=0 loops=1)
   Hash Cond: ((location.key = area.key) AND (location.area_id = area.id))
   Join Filter: (((area.tr_from < location.tr_until) AND (area.tr_until >= location.tr_until)) OR ((area.tr_from = location.tr_until) AND (area.tr_until = locat
ion.tr_until)))
   ->  Seq Scan on location  (cost=0.00..79.91 rows=2391 width=126) (actual time=0.005..1.072 rows=2393 loops=1)
   ->  Hash  (cost=8.87..8.87 rows=387 width=37) (actual time=0.509..0.509 rows=387 loops=1)
         ->  Seq Scan on area  (cost=0.00..8.87 rows=387 width=37) (actual time=0.007..0.239 rows=387 loops=1)
 Total runtime: 5.725 ms
(7 rows)

This makes no sense to me.

Any thoughts?

Thanks.

+1  A: 

You're setting up the trigger to run for each row, and then inside the trigger function you're doing another select on the whole table. Do one or the other. (Try changing FOR EACH ROW to FOR EACH STATEMENT.)

Pointy
Tried that, no difference :-(
Mark
Hmm. Well, have you looked at the query plan when you OR the two queries together? Can you combine the two queries by moving the logic into a single WHERE clause?
Pointy
Not yet - what I might try is trying the function with one of the queries missing in case it is doing some odd sort of mashing together of the 2 queries. I'll report back..
Mark
OK - I'm not a super database genius, but one of the things I've learned is that "OR" has a way of causing database servers to get stupid.
Pointy
I've reduced the function to just do the first query. It takes exactly half the speed, so there's still an issue. I really don't understand how me running the query manually can amount to being 10's of times faster than running it when triggered :-(
Mark
No probs, appreciate the help though :-) And yes agree that OR'ing can send things a bit crazy, but I've eliminated that now and no difference.
Mark
Do you have indexes on those "key" and "id" columns? I wonder if those inner queries are making it slower because the cache is getting blown. Those "Seq scan" passes seem suspicious.
Pointy
But these would also effect the query when ran manually, I assume..
Mark
Yes, they would, but what I'm wondering is whether the work being done in your transaction (the INSERT or UPDATE or DELETE or whatever) makes some difference, esp. regarding how the cache is affected. Again, however, I'm no database genius. All I know is that the database genius I do know would probably not like those "seq scan" steps.
Pointy
If I remove the trigger, the speed jumps right up, so it's not from doing the update. It is purely that trigger causing the speed problem from what I can tell. I'll investigate the sequential scan - there are indexes but maybe I'm missing one.
Mark
OK - yes I agree that it's the trigger; what I meant was that there may be something about the *context* when it runs *as a trigger* that has a weird effect. But I suppose that's kind-of the whole point of your question :-)
Pointy