Обсуждение: FK triggers misused?

От:
cluster
Дата:

I have performance problem with the following simple update query:

   UPDATE posts
   SET num_views = num_views + 1
   WHERE post_id IN (2526,5254,2572,4671,25);

The table "posts" is a large table with a number of foreign keys (FK).

It seems that the FK triggers for the table are evaluated even though
none of the FK columns are altered. In fact, these FK triggers seems to
constitute a considerable part of the total execution time. See the
below EXPLAIN ANALYZE.

Why are these FK triggers evaluated at all and why do they take so much
time?

------
=> EXPLAIN ANALYZE update posts set num_views = num_views + 1 where
post_id in (2526,5254,2572,4671,25);
                                                        QUERY PLAN

-------------------------------------------------------------------------------------------------------------------------
  Bitmap Heap Scan on posts  (cost=10.02..29.81 rows=5 width=1230)
(actual time=0.146..0.253 rows=5 loops=1)
    Recheck Cond: ((post_id = 2526) OR (post_id = 5254) OR (post_id =
2572) OR (post_id = 4671) OR (post_id = 25))
    ->  BitmapOr  (cost=10.02..10.02 rows=5 width=0) (actual
time=0.105..0.105 rows=0 loops=1)
          ->  Bitmap Index Scan on posts_pkey  (cost=0.00..2.00 rows=1
width=0) (actual time=0.053..0.053 rows=2 loops=1)
                Index Cond: (post_id = 2526)
          ->  Bitmap Index Scan on posts_pkey  (cost=0.00..2.00 rows=1
width=0) (actual time=0.012..0.012 rows=2 loops=1)
                Index Cond: (post_id = 5254)
          ->  Bitmap Index Scan on posts_pkey  (cost=0.00..2.00 rows=1
width=0) (actual time=0.008..0.008 rows=2 loops=1)
                Index Cond: (post_id = 2572)
          ->  Bitmap Index Scan on posts_pkey  (cost=0.00..2.00 rows=1
width=0) (actual time=0.010..0.010 rows=2 loops=1)
                Index Cond: (post_id = 4671)
          ->  Bitmap Index Scan on posts_pkey  (cost=0.00..2.00 rows=1
width=0) (actual time=0.011..0.011 rows=2 loops=1)
                Index Cond: (post_id = 25)
  Trigger for constraint posts_question_id_fkey: time=50.031 calls=5
  Trigger for constraint posts_author_id_fkey: time=22.330 calls=5
  Trigger for constraint posts_language_id_fkey: time=1.282 calls=5
  Trigger posts_tsvectorupdate: time=61.659 calls=5
  Total runtime: 174.230 ms
(18 rows)

От:
Tom Lane
Дата:

cluster <> writes:
> It seems that the FK triggers for the table are evaluated even though
> none of the FK columns are altered.

Hm, they're not supposed to be, at least not in reasonably modern
PG releases (and one that breaks out trigger runtime in EXPLAIN ANALYZE
should be modern enough IIRC).  Exactly which PG release are you
running?  Can you provide a self-contained test case?

            regards, tom lane

От:
Andrew - Supernews
Дата:

On 2007-04-15, Tom Lane <> wrote:
> cluster <> writes:
>> It seems that the FK triggers for the table are evaluated even though
>> none of the FK columns are altered.
>
> Hm, they're not supposed to be, at least not in reasonably modern
> PG releases (and one that breaks out trigger runtime in EXPLAIN ANALYZE
> should be modern enough IIRC).  Exactly which PG release are you
> running?  Can you provide a self-contained test case?

Looking at current CVS code the RI check seems to be skipped on update of
the _referred to_ table if the old and new values match, but not on update
of the _referring_ table.

--
Andrew, Supernews
http://www.supernews.com - individual and corporate NNTP services

От:
Tom Lane
Дата:

Andrew - Supernews <> writes:
> Looking at current CVS code the RI check seems to be skipped on update of
> the _referred to_ table if the old and new values match, but not on update
> of the _referring_ table.

No, both sides are supposed to be tested, see lines 3350-3395 in
src/backend/commands/trigger.c.  Or do you see something broken there?
It works for me in a quick test.

            regards, tom lane

От:
Andrew - Supernews
Дата:

On 2007-04-15, Tom Lane <> wrote:
> Andrew - Supernews <> writes:
>> Looking at current CVS code the RI check seems to be skipped on update of
>> the _referred to_ table if the old and new values match, but not on update
>> of the _referring_ table.
>
> No, both sides are supposed to be tested, see lines 3350-3395 in
> src/backend/commands/trigger.c.  Or do you see something broken there?
> It works for me in a quick test.

Hm, you're right; I was looking at the logic in the triggers themselves
(in ri_triggers.c).

So the next question is, what pg version is the original poster using?
because 8.1.x doesn't report trigger execution times, and 8.2.x would use
a single bitmap index scan with an = ANY condition, not a BitmapOr.

--
Andrew, Supernews
http://www.supernews.com - individual and corporate NNTP services

От:
cluster
Дата:

> So the next question is, what pg version is the original poster using?
> because 8.1.x doesn't report trigger execution times, and 8.2.x would use
> a single bitmap index scan with an = ANY condition, not a BitmapOr.

I have tried 8.1.0 and 8.1.3 for this query.

От:
Tom Lane
Дата:

cluster <> writes:
>> So the next question is, what pg version is the original poster using?
>> because 8.1.x doesn't report trigger execution times, and 8.2.x would use
>> a single bitmap index scan with an = ANY condition, not a BitmapOr.

> I have tried 8.1.0 and 8.1.3 for this query.

Checking the code, 8.1.x does report trigger times, so AndrewSN is
mistaken on that point.

However, it's also the case that 8.1 does have the suppress-the-trigger
logic for FKs, and it works fine for me in a simple test.  I'm using
8.1 branch tip, but there are no relevant changes since 8.1.0 as far
as I can see in the CVS logs.

What is that non-FK trigger shown in your results?

>    Trigger posts_tsvectorupdate: time=61.659 calls=5

Could it possibly be firing an extra update on the table?

            regards, tom lane

От:
Tom Lane
Дата:

Idly thumbing through the code, I came across something that might
possibly explain your results.  Do the rows being updated contain
NULLs in the foreign-key columns?  I see that ri_KeysEqual() treats
two null values as not equal, which might be overzealous respect for
SQL null semantics in this context.

            regards, tom lane

От:
cluster
Дата:

 > Do the rows being updated contain
> NULLs in the foreign-key columns?

No, all FK columns are non-NULL. It is very strange.

От:
cluster
Дата:

I have investigated a bit now and found the following:

When I perform the update the *first* time, the triggers are actually
not evaluated. But from the second update they are.

Also notice that the number of rows changes. Shouldn't that number of
rows always be 2 as question_id is primary key?

Example:

=> explain analyze update questions set cancelled_time = now() where
question_id in (10,11);
                                                          QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------
  Bitmap Heap Scan on questions  (cost=4.01..12.04 rows=2 width=112)
(actual time=0.193..0.205 rows=2 loops=1)
    Recheck Cond: ((question_id = 10) OR (question_id = 11))
    ->  BitmapOr  (cost=4.01..4.01 rows=2 width=0) (actual
time=0.046..0.046 rows=0 loops=1)
          ->  Bitmap Index Scan on questions_pkey  (cost=0.00..2.00
rows=1 width=0) (actual time=0.037..0.037 rows=1 loops=1)
                Index Cond: (question_id = 10)
          ->  Bitmap Index Scan on questions_pkey  (cost=0.00..2.00
rows=1 width=0) (actual time=0.005..0.005 rows=1 loops=1)
                Index Cond: (question_id = 11)
  Trigger for constraint questions_repost_of_fkey: time=0.023 calls=2
  Total runtime: 0.734 ms
(9 rows)



=> explain analyze update questions set cancelled_time = now() where
question_id in (10,11);
                                                          QUERY PLAN

-----------------------------------------------------------------------------------------------------------------------------
  Bitmap Heap Scan on questions  (cost=4.01..12.04 rows=2 width=112)
(actual time=0.085..0.097 rows=2 loops=1)
    Recheck Cond: ((question_id = 10) OR (question_id = 11))
    ->  BitmapOr  (cost=4.01..4.01 rows=2 width=0) (actual
time=0.047..0.047 rows=0 loops=1)
          ->  Bitmap Index Scan on questions_pkey  (cost=0.00..2.00
rows=1 width=0) (actual time=0.036..0.036 rows=2 loops=1)
                Index Cond: (question_id = 10)
          ->  Bitmap Index Scan on questions_pkey  (cost=0.00..2.00
rows=1 width=0) (actual time=0.007..0.007 rows=2 loops=1)
                Index Cond: (question_id = 11)
  Trigger for constraint questions_repost_of_fkey: time=0.025 calls=2
  Trigger for constraint questions_author_id_fkey: time=0.167 calls=2
  Trigger for constraint questions_category_id_fkey: time=0.196 calls=2
  Trigger for constraint questions_lock_user_id_fkey: time=0.116 calls=2
  Total runtime: 1.023 ms
(12 rows)



От:
Stephan Szabo
Дата:

On Sat, 21 Apr 2007, cluster wrote:

> I have investigated a bit now and found the following:
>
> When I perform the update the *first* time, the triggers are actually
> not evaluated. But from the second update they are.

Are these in one transaction? If so, then right now after the first
update, the remaining updates will trigger checks if the row modified was
modified in this transaction.  The comment in trigger.c lists the basic
circumstance, although it mentions it in terms of insert and a deferred
FK, I would guess that if there's ever a possibility of two modifications
(including multiple updates or on an immediate constraint) before the
constraint check occurred the same condition could happen.