]> granicus.if.org Git - postgresql/commitdiff
Back-patch change to avoid O(N^2) behavior with lots of deferred triggers,
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 19 May 2003 17:23:54 +0000 (17:23 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 19 May 2003 17:23:54 +0000 (17:23 +0000)
by making deferredTriggerInvokeEvents only scan events added since it last ran.

src/backend/commands/trigger.c

index 5aed30f7edc1bd1e48e79e0ca7fbfb2584be9c83..09c593db37a1a961f83701578b992fdcced91c6e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.136.2.1 2003/03/27 14:33:20 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.136.2.2 2003/05/19 17:23:54 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1461,12 +1461,18 @@ static List *deftrig_trigstates;
  * Because this can grow pretty large, we don't use separate List nodes,
  * but instead thread the list through the dte_next fields of the member
  * nodes.  Saves just a few bytes per entry, but that adds up.
+ * 
+ * deftrig_events_imm holds the tail pointer as of the last 
+ * deferredTriggerInvokeEvents call; we can use this to avoid rescanning
+ * entries unnecessarily.  It is NULL if deferredTriggerInvokeEvents
+ * hasn't run since the last state change.
  *
  * XXX Need to be able to shove this data out to a file if it grows too
  *        large...
  * ----------
  */
 static DeferredTriggerEvent deftrig_events;
+static DeferredTriggerEvent deftrig_events_imm;
 static DeferredTriggerEvent deftrig_event_tail;
 
 
@@ -1680,7 +1686,7 @@ static void
 deferredTriggerInvokeEvents(bool immediate_only)
 {
        DeferredTriggerEvent event,
-                               prev_event = NULL;
+                               prev_event;
        MemoryContext per_tuple_context;
        Relation        rel = NULL;
        TriggerDesc *trigdesc = NULL;
@@ -1692,13 +1698,12 @@ deferredTriggerInvokeEvents(bool immediate_only)
         * are going to discard the whole event queue on return anyway, so no
         * need to bother with "retail" pfree's.
         *
-        * In a scenario with many commands in a transaction and many
-        * deferred-to-end-of-transaction triggers, it could get annoying to
-        * rescan all the deferred triggers at each command end. To speed this
-        * up, we could remember the actual end of the queue at EndQuery and
-        * examine only events that are newer. On state changes we simply
-        * reset the saved position to the beginning of the queue and process
-        * all events once with the new states.
+        * If immediate_only is true, we need only scan from where the end of
+        * the queue was at the previous deferredTriggerInvokeEvents call;
+        * any non-deferred events before that point are already fired.
+        * (But if the deferral state changes, we must reset the saved position
+        * to the beginning of the queue, so as to process all events once with
+        * the new states.  See DeferredTriggerSetState.)
         */
 
        /* Make a per-tuple memory context for trigger function calls */
@@ -1709,7 +1714,22 @@ deferredTriggerInvokeEvents(bool immediate_only)
                                                          ALLOCSET_DEFAULT_INITSIZE,
                                                          ALLOCSET_DEFAULT_MAXSIZE);
 
-       event = deftrig_events;
+       /*
+        * If immediate_only is true, then the only events that could need firing
+        * are those since deftrig_events_imm.  (But if deftrig_events_imm is
+        * NULL, we must scan the entire list.)
+        */
+       if (immediate_only && deftrig_events_imm != NULL)
+       {
+               prev_event = deftrig_events_imm;
+               event = prev_event->dte_next;
+       }
+       else
+       {
+               prev_event = NULL;
+               event = deftrig_events;
+       }
+
        while (event != NULL)
        {
                bool            still_deferred_ones = false;
@@ -1830,6 +1850,9 @@ deferredTriggerInvokeEvents(bool immediate_only)
        /* Update list tail pointer in case we just deleted tail event */
        deftrig_event_tail = prev_event;
 
+       /* Set the immediate event pointer for next time */
+       deftrig_events_imm = prev_event;
+
        /* Release working resources */
        if (rel)
                heap_close(rel, NoLock);
@@ -1917,6 +1940,7 @@ DeferredTriggerBeginXact(void)
        MemoryContextSwitchTo(oldcxt);
 
        deftrig_events = NULL;
+       deftrig_events_imm = NULL;
        deftrig_event_tail = NULL;
 }
 
@@ -2146,8 +2170,11 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
         * CONSTRAINTS command applies retroactively. This happens "for free"
         * since we have already made the necessary modifications to the
         * constraints, and deferredTriggerEndQuery() is called by
-        * finish_xact_command().
+        * finish_xact_command().  But we must reset deferredTriggerInvokeEvents'
+        * tail pointer to make it rescan the entire list, in case some deferred
+        * events are now immediately invokable.
         */
+       deftrig_events_imm = NULL;
 }