diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-01-04 17:42:19 -0500 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-01-04 17:42:19 -0500 |
commit | 94afbd5831fbc1926f1c367ac14a45ccc29d313d (patch) | |
tree | 51cea3e79490853693f795ac03c2438dd9b5522a /src/backend/executor/spi.c | |
parent | 78a5e738e97b4dda89e1bfea60675bcf15f25994 (diff) | |
download | postgresql-94afbd5831fbc1926f1c367ac14a45ccc29d313d.tar.gz postgresql-94afbd5831fbc1926f1c367ac14a45ccc29d313d.zip |
Invent a "one-shot" variant of CachedPlans for better performance.
SPI_execute() and related functions create a CachedPlan, execute it once,
and immediately discard it, so that the functionality offered by
plancache.c is of no value in this code path. And performance measurements
show that the extra data copying and invalidation checking done by
plancache.c slows down simple queries by 10% or more compared to 9.1.
However, enough of the SPI code is shared with functions that do need plan
caching that it seems impractical to bypass plancache.c altogether.
Instead, let's invent a variant version of cached plans that preserves
99% of the API but doesn't offer any of the actual functionality, nor the
overhead. This puts SPI_execute() performance back on par, or maybe even
slightly better, than it was before. This change should resolve recent
complaints of performance degradation from Dong Ye, Pavel Stehule, and
others.
By avoiding data copying, this change also reduces the amount of memory
needed to execute many-statement SPI_execute() strings, as for instance in
a recent complaint from Tomas Vondra.
An additional benefit of this change is that multi-statement SPI_execute()
query strings are now processed fully serially, that is we complete
execution of earlier statements before running parse analysis and planning
on following ones. This eliminates a long-standing POLA violation, in that
DDL that affects the behavior of a later statement will now behave as
expected.
Back-patch to 9.2, since this was a performance regression compared to 9.1.
(In 9.2, place the added struct fields so as to avoid changing the offsets
of existing fields.)
Heikki Linnakangas and Tom Lane
Diffstat (limited to 'src/backend/executor/spi.c')
-rw-r--r-- | src/backend/executor/spi.c | 144 |
1 files changed, 128 insertions, 16 deletions
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 9b167af6608..3da2d263023 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -49,8 +49,9 @@ static int _SPI_curid = -1; static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamListInfo paramLI, bool read_only); -static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, - ParamListInfo boundParams); +static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan); + +static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan); static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, @@ -355,7 +356,7 @@ SPI_execute(const char *src, bool read_only, long tcount) plan.magic = _SPI_PLAN_MAGIC; plan.cursor_options = 0; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_oneshot_plan(src, &plan); res = _SPI_execute_plan(&plan, NULL, InvalidSnapshot, InvalidSnapshot, @@ -506,7 +507,7 @@ SPI_execute_with_args(const char *src, paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); - _SPI_prepare_plan(src, &plan, paramLI); + _SPI_prepare_oneshot_plan(src, &plan); res = _SPI_execute_plan(&plan, paramLI, InvalidSnapshot, InvalidSnapshot, @@ -547,7 +548,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, plan.parserSetup = NULL; plan.parserSetupArg = NULL; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); @@ -584,7 +585,7 @@ SPI_prepare_params(const char *src, plan.parserSetup = parserSetup; plan.parserSetupArg = parserSetupArg; - _SPI_prepare_plan(src, &plan, NULL); + _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); @@ -599,7 +600,8 @@ SPI_keepplan(SPIPlanPtr plan) { ListCell *lc; - if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved) + if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || + plan->saved || plan->oneshot) return SPI_ERROR_ARGUMENT; /* @@ -1083,7 +1085,7 @@ SPI_cursor_open_with_args(const char *name, paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); - _SPI_prepare_plan(src, &plan, paramLI); + _SPI_prepare_plan(src, &plan); /* We needn't copy the plan; SPI_cursor_open_internal will do so */ @@ -1645,10 +1647,6 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup * and plan->parserSetupArg) must be valid, as must plan->cursor_options. - * If boundParams isn't NULL then it represents parameter values that are made - * available to the planner (as either estimates or hard values depending on - * their PARAM_FLAG_CONST marking). The boundParams had better match the - * param type information embedded in the plan! * * Results are stored into *plan (specifically, plan->plancache_list). * Note that the result data is all in CurrentMemoryContext or child contexts @@ -1657,13 +1655,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) * parsing is also left in CurrentMemoryContext. */ static void -_SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) +_SPI_prepare_plan(const char *src, SPIPlanPtr plan) { List *raw_parsetree_list; List *plancache_list; ListCell *list_item; ErrorContextCallback spierrcontext; - int cursor_options = plan->cursor_options; /* * Setup error traceback support for ereport() @@ -1726,13 +1723,80 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams) plan->nargs, plan->parserSetup, plan->parserSetupArg, - cursor_options, + plan->cursor_options, false); /* not fixed result */ plancache_list = lappend(plancache_list, plansource); } plan->plancache_list = plancache_list; + plan->oneshot = false; + + /* + * Pop the error context stack + */ + error_context_stack = spierrcontext.previous; +} + +/* + * Parse, but don't analyze, a querystring. + * + * This is a stripped-down version of _SPI_prepare_plan that only does the + * initial raw parsing. It creates "one shot" CachedPlanSources + * that still require parse analysis before execution is possible. + * + * The advantage of using the "one shot" form of CachedPlanSource is that + * we eliminate data copying and invalidation overhead. Postponing parse + * analysis also prevents issues if some of the raw parsetrees are DDL + * commands that affect validity of later parsetrees. Both of these + * attributes are good things for SPI_execute() and similar cases. + * + * Results are stored into *plan (specifically, plan->plancache_list). + * Note that the result data is all in CurrentMemoryContext or child contexts + * thereof; in practice this means it is in the SPI executor context, and + * what we are creating is a "temporary" SPIPlan. Cruft generated during + * parsing is also left in CurrentMemoryContext. + */ +static void +_SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan) +{ + List *raw_parsetree_list; + List *plancache_list; + ListCell *list_item; + ErrorContextCallback spierrcontext; + + /* + * Setup error traceback support for ereport() + */ + spierrcontext.callback = _SPI_error_callback; + spierrcontext.arg = (void *) src; + spierrcontext.previous = error_context_stack; + error_context_stack = &spierrcontext; + + /* + * Parse the request string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(src); + + /* + * Construct plancache entries, but don't do parse analysis yet. + */ + plancache_list = NIL; + + foreach(list_item, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(list_item); + CachedPlanSource *plansource; + + plansource = CreateOneShotCachedPlan(parsetree, + src, + CreateCommandTag(parsetree)); + + plancache_list = lappend(plancache_list, plansource); + } + + plan->plancache_list = plancache_list; + plan->oneshot = true; /* * Pop the error context stack @@ -1770,7 +1834,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, * Setup error traceback support for ereport() */ spierrcontext.callback = _SPI_error_callback; - spierrcontext.arg = NULL; + spierrcontext.arg = NULL; /* we'll fill this below */ spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; @@ -1817,6 +1881,47 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, spierrcontext.arg = (void *) plansource->query_string; /* + * If this is a one-shot plan, we still need to do parse analysis. + */ + if (plan->oneshot) + { + Node *parsetree = plansource->raw_parse_tree; + const char *src = plansource->query_string; + List *stmt_list; + + /* + * Parameter datatypes are driven by parserSetup hook if provided, + * otherwise we use the fixed parameter list. + */ + if (plan->parserSetup != NULL) + { + Assert(plan->nargs == 0); + stmt_list = pg_analyze_and_rewrite_params(parsetree, + src, + plan->parserSetup, + plan->parserSetupArg); + } + else + { + stmt_list = pg_analyze_and_rewrite(parsetree, + src, + plan->argtypes, + plan->nargs); + } + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + stmt_list, + NULL, + plan->argtypes, + plan->nargs, + plan->parserSetup, + plan->parserSetupArg, + plan->cursor_options, + false); /* not fixed result */ + } + + /* * Replan if needed, and increment plan refcount. If it's a saved * plan, the refcount must be backed by the CurrentResourceOwner. */ @@ -2313,6 +2418,8 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan) /* Assert the input is a temporary SPIPlan */ Assert(plan->magic == _SPI_PLAN_MAGIC); Assert(plan->plancxt == NULL); + /* One-shot plans can't be saved */ + Assert(!plan->oneshot); /* * Create a memory context for the plan, underneath the procedure context. @@ -2330,6 +2437,7 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan) newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; + newplan->oneshot = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; @@ -2379,6 +2487,9 @@ _SPI_save_plan(SPIPlanPtr plan) MemoryContext oldcxt; ListCell *lc; + /* One-shot plans can't be saved */ + Assert(!plan->oneshot); + /* * Create a memory context for the plan. We don't expect the plan to be * very large, so use smaller-than-default alloc parameters. It's a @@ -2395,6 +2506,7 @@ _SPI_save_plan(SPIPlanPtr plan) newplan = (SPIPlanPtr) palloc(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->saved = false; + newplan->oneshot = false; newplan->plancache_list = NIL; newplan->plancxt = plancxt; newplan->cursor_options = plan->cursor_options; |