diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index fff2f4c8bad..d9a2f9bf695 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -612,7 +612,20 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * the SubqueryScanPath as not parallel-safe. (Note that * set_subquery_pathlist() might push some of these quals down * into the subquery itself, but that doesn't change anything.) + * + * We can't push sub-select containing LIMIT/OFFSET to workers as + * there is no guarantee that the row order will be fully + * deterministic, and applying LIMIT/OFFSET will lead to + * inconsistent results at the top-level. (In some cases, where + * the result is ordered, we could relax this restriction. But it + * doesn't currently seem worth expending extra effort to do so.) */ + { + Query *subquery = castNode(Query, rte->subquery); + + if (limit_needed(subquery)) + return; + } break; case RTE_JOIN: diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1946f9ef968..a8a025cb853 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -121,7 +121,6 @@ static void preprocess_rowmarks(PlannerInfo *root); static double preprocess_limit(PlannerInfo *root, double tuple_fraction, int64 *offset_est, int64 *count_est); -static bool limit_needed(Query *parse); static void remove_useless_groupby_columns(PlannerInfo *root); static List *preprocess_groupclause(PlannerInfo *root, List *force); static List *extract_rollup_sets(List *groupingSets); @@ -2697,7 +2696,7 @@ preprocess_limit(PlannerInfo *root, double tuple_fraction, * a key distinction: here we need hard constants in OFFSET/LIMIT, whereas * in preprocess_limit it's good enough to consider estimated values. */ -static bool +bool limit_needed(Query *parse) { Node *node; diff --git a/src/include/optimizer/planner.h b/src/include/optimizer/planner.h index 2a4cf71e102..acb709a5552 100644 --- a/src/include/optimizer/planner.h +++ b/src/include/optimizer/planner.h @@ -46,6 +46,8 @@ extern bool is_dummy_plan(Plan *plan); extern RowMarkType select_rowmark_type(RangeTblEntry *rte, LockClauseStrength strength); +extern bool limit_needed(Query *parse); + extern void mark_partial_aggref(Aggref *agg, AggSplit aggsplit); extern Path *get_cheapest_fractional_path(RelOptInfo *rel, diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out index 9fd86256d2f..2f2aac22548 100644 --- a/src/test/regress/expected/select_parallel.out +++ b/src/test/regress/expected/select_parallel.out @@ -599,6 +599,25 @@ explain (costs off, verbose) Output: b.unique1 (18 rows) +-- LIMIT/OFFSET within sub-selects can't be pushed to workers. +explain (costs off) + select * from tenk1 a where two in + (select two from tenk1 b where stringu1 like '%AAAA' limit 3); + QUERY PLAN +--------------------------------------------------------------- + Hash Semi Join + Hash Cond: (a.two = b.two) + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk1 a + -> Hash + -> Limit + -> Gather + Workers Planned: 4 + -> Parallel Seq Scan on tenk1 b + Filter: (stringu1 ~~ '%AAAA'::text) +(11 rows) + -- to increase the parallel query test coverage EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1; QUERY PLAN diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql index dd9365d7c6e..b1c1d776290 100644 --- a/src/test/regress/sql/select_parallel.sql +++ b/src/test/regress/sql/select_parallel.sql @@ -210,6 +210,11 @@ explain (costs off, verbose) (select unique1, row_number() over() from tenk1 b); +-- LIMIT/OFFSET within sub-selects can't be pushed to workers. +explain (costs off) + select * from tenk1 a where two in + (select two from tenk1 b where stringu1 like '%AAAA' limit 3); + -- to increase the parallel query test coverage EXPLAIN (analyze, timing off, summary off, costs off) SELECT * FROM tenk1;