aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/trigger.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2003-04-20 17:03:25 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2003-04-20 17:03:25 +0000
commitb40bc9eac6e1679676857109b1250fad9d725398 (patch)
treec9ec853f3a190ab601b36d83ca86b0deee1440b4 /src/backend/commands/trigger.c
parent7773434999b99163e8b620c543233d4a3bd26497 (diff)
downloadpostgresql-b40bc9eac6e1679676857109b1250fad9d725398.tar.gz
postgresql-b40bc9eac6e1679676857109b1250fad9d725398.zip
Avoid O(N^2) behavior with lots of deferred triggers by making
deferredTriggerInvokeEvents only scan events added since it last ran. Stephan Szabo, some corrections by Tom Lane.
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r--src/backend/commands/trigger.c49
1 files changed, 38 insertions, 11 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d6f0f03666d..8a62986ec44 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -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.147 2003/03/31 20:47:51 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.148 2003/04/20 17:03:25 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1626,12 +1626,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;
@@ -1845,7 +1851,7 @@ static void
deferredTriggerInvokeEvents(bool immediate_only)
{
DeferredTriggerEvent event,
- prev_event = NULL;
+ prev_event;
MemoryContext per_tuple_context;
Relation rel = NULL;
TriggerDesc *trigdesc = NULL;
@@ -1857,13 +1863,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 */
@@ -1874,7 +1879,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;
@@ -1993,6 +2013,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);
@@ -2051,6 +2074,7 @@ DeferredTriggerBeginXact(void)
deftrig_trigstates = NIL;
deftrig_events = NULL;
+ deftrig_events_imm = NULL;
deftrig_event_tail = NULL;
}
@@ -2280,8 +2304,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;
}