mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
Allow DML commands that create tables to use parallel query.
Haribabu Kommi, reviewed by Dilip Kumar and Rafia Sabih. Various cosmetic changes by me to explain why this appears to be safe but allowing inserts in parallel mode in general wouldn't be. Also, I removed the REFRESH MATERIALIZED VIEW case from Haribabu's patch, since I'm not convinced that case is OK, and hacked on the documentation somewhat. Discussion: http://postgr.es/m/CAJrrPGdo5bak6qnPWe8Kpi8g_jfQEs-G4SYmG9y+OFaw2-dPvA@mail.gmail.com
This commit is contained in:
@ -151,9 +151,10 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
|
|||||||
<para>
|
<para>
|
||||||
The query writes any data or locks any database rows. If a query
|
The query writes any data or locks any database rows. If a query
|
||||||
contains a data-modifying operation either at the top level or within
|
contains a data-modifying operation either at the top level or within
|
||||||
a CTE, no parallel plans for that query will be generated. This is a
|
a CTE, no parallel plans for that query will be generated. As an
|
||||||
limitation of the current implementation which could be lifted in a
|
exception, the commands <literal>CREATE TABLE</>, <literal>SELECT
|
||||||
future release.
|
INTO</>, and <literal>CREATE MATERIALIZED VIEW</> which create a new
|
||||||
|
table and populate it can use a parallel plan.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
@ -241,15 +242,6 @@ EXPLAIN SELECT * FROM pgbench_accounts WHERE filler LIKE '%x%';
|
|||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
|
|
||||||
<listitem>
|
|
||||||
<para>
|
|
||||||
A prepared statement is executed using a <literal>CREATE TABLE .. AS
|
|
||||||
EXECUTE ..</literal> statement. This construct converts what otherwise
|
|
||||||
would have been a read-only operation into a read-write operation,
|
|
||||||
making it ineligible for parallel query.
|
|
||||||
</para>
|
|
||||||
</listitem>
|
|
||||||
|
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
The transaction isolation level is serializable. This situation
|
The transaction isolation level is serializable. This situation
|
||||||
|
@ -2598,15 +2598,17 @@ heap_prepare_insert(Relation relation, HeapTuple tup, TransactionId xid,
|
|||||||
CommandId cid, int options)
|
CommandId cid, int options)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* For now, parallel operations are required to be strictly read-only.
|
* Parallel operations are required to be strictly read-only in a parallel
|
||||||
* Unlike heap_update() and heap_delete(), an insert should never create a
|
* worker. Parallel inserts are not safe even in the leader in the
|
||||||
* combo CID, so it might be possible to relax this restriction, but not
|
* general case, because group locking means that heavyweight locks for
|
||||||
* without more thought and testing.
|
* relation extension or GIN page locks will not conflict between members
|
||||||
|
* of a lock group, but we don't prohibit that case here because there are
|
||||||
|
* useful special cases that we can safely allow, such as CREATE TABLE AS.
|
||||||
*/
|
*/
|
||||||
if (IsInParallelMode())
|
if (IsParallelWorker())
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
|
||||||
errmsg("cannot insert tuples during a parallel operation")));
|
errmsg("cannot insert tuples in a parallel worker")));
|
||||||
|
|
||||||
if (relation->rd_rel->relhasoids)
|
if (relation->rd_rel->relhasoids)
|
||||||
{
|
{
|
||||||
|
@ -326,8 +326,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
|
|||||||
query = linitial_node(Query, rewritten);
|
query = linitial_node(Query, rewritten);
|
||||||
Assert(query->commandType == CMD_SELECT);
|
Assert(query->commandType == CMD_SELECT);
|
||||||
|
|
||||||
/* plan the query --- note we disallow parallelism */
|
/* plan the query */
|
||||||
plan = pg_plan_query(query, 0, params);
|
plan = pg_plan_query(query, CURSOR_OPT_PARALLEL_OK, params);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Use a snapshot with an updated command ID to ensure this query sees
|
* Use a snapshot with an updated command ID to ensure this query sees
|
||||||
|
@ -396,8 +396,6 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
|
|||||||
* We have to rewrite the contained SELECT and then pass it back to
|
* We have to rewrite the contained SELECT and then pass it back to
|
||||||
* ExplainOneQuery. It's probably not really necessary to copy the
|
* ExplainOneQuery. It's probably not really necessary to copy the
|
||||||
* contained parsetree another time, but let's be safe.
|
* contained parsetree another time, but let's be safe.
|
||||||
*
|
|
||||||
* Like ExecCreateTableAs, disallow parallelism in the plan.
|
|
||||||
*/
|
*/
|
||||||
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
|
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
|
||||||
List *rewritten;
|
List *rewritten;
|
||||||
@ -405,7 +403,7 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
|
|||||||
rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
|
rewritten = QueryRewrite(castNode(Query, copyObject(ctas->query)));
|
||||||
Assert(list_length(rewritten) == 1);
|
Assert(list_length(rewritten) == 1);
|
||||||
ExplainOneQuery(linitial_node(Query, rewritten),
|
ExplainOneQuery(linitial_node(Query, rewritten),
|
||||||
0, ctas->into, es,
|
CURSOR_OPT_PARALLEL_OK, ctas->into, es,
|
||||||
queryString, params, queryEnv);
|
queryString, params, queryEnv);
|
||||||
}
|
}
|
||||||
else if (IsA(utilityStmt, DeclareCursorStmt))
|
else if (IsA(utilityStmt, DeclareCursorStmt))
|
||||||
|
@ -1697,11 +1697,9 @@ ExecutePlan(EState *estate,
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* If the plan might potentially be executed multiple times, we must force
|
* If the plan might potentially be executed multiple times, we must force
|
||||||
* it to run without parallelism, because we might exit early. Also
|
* it to run without parallelism, because we might exit early.
|
||||||
* disable parallelism when writing into a relation, because no database
|
|
||||||
* changes are allowed in parallel mode.
|
|
||||||
*/
|
*/
|
||||||
if (!execute_once || dest->mydest == DestIntoRel)
|
if (!execute_once)
|
||||||
use_parallel_mode = false;
|
use_parallel_mode = false;
|
||||||
|
|
||||||
if (use_parallel_mode)
|
if (use_parallel_mode)
|
||||||
|
@ -257,6 +257,16 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
|
|||||||
* to values that don't permit parallelism, or if parallel-unsafe
|
* to values that don't permit parallelism, or if parallel-unsafe
|
||||||
* functions are present in the query tree.
|
* functions are present in the query tree.
|
||||||
*
|
*
|
||||||
|
* (Note that we do allow CREATE TABLE AS, SELECT INTO, and CREATE
|
||||||
|
* MATERIALIZED VIEW to use parallel plans, but this is safe only because
|
||||||
|
* the command is writing into a completely new table which workers won't
|
||||||
|
* be able to see. If the workers could see the table, the fact that
|
||||||
|
* group locking would cause them to ignore the leader's heavyweight
|
||||||
|
* relation extension lock and GIN page locks would make this unsafe.
|
||||||
|
* We'll have to fix that somehow if we want to allow parallel inserts in
|
||||||
|
* general; updates and deletes have additional problems especially around
|
||||||
|
* combo CIDs.)
|
||||||
|
*
|
||||||
* For now, we don't try to use parallel mode if we're running inside a
|
* For now, we don't try to use parallel mode if we're running inside a
|
||||||
* parallel worker. We might eventually be able to relax this
|
* parallel worker. We might eventually be able to relax this
|
||||||
* restriction, but for now it seems best not to have parallel workers
|
* restriction, but for now it seems best not to have parallel workers
|
||||||
|
79
src/test/regress/expected/write_parallel.out
Normal file
79
src/test/regress/expected/write_parallel.out
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
--
|
||||||
|
-- PARALLEL
|
||||||
|
--
|
||||||
|
-- Serializable isolation would disable parallel query, so explicitly use an
|
||||||
|
-- arbitrary other level.
|
||||||
|
begin isolation level repeatable read;
|
||||||
|
-- encourage use of parallel plans
|
||||||
|
set parallel_setup_cost=0;
|
||||||
|
set parallel_tuple_cost=0;
|
||||||
|
set min_parallel_table_scan_size=0;
|
||||||
|
set max_parallel_workers_per_gather=4;
|
||||||
|
--
|
||||||
|
-- Test write operations that has an underlying query that is eligble
|
||||||
|
-- for parallel plans
|
||||||
|
--
|
||||||
|
explain (costs off) create table parallel_write as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
QUERY PLAN
|
||||||
|
---------------------------------------------------
|
||||||
|
Finalize HashAggregate
|
||||||
|
Group Key: (length((stringu1)::text))
|
||||||
|
-> Gather
|
||||||
|
Workers Planned: 4
|
||||||
|
-> Partial HashAggregate
|
||||||
|
Group Key: length((stringu1)::text)
|
||||||
|
-> Parallel Seq Scan on tenk1
|
||||||
|
(7 rows)
|
||||||
|
|
||||||
|
create table parallel_write as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
drop table parallel_write;
|
||||||
|
explain (costs off) select length(stringu1) into parallel_write
|
||||||
|
from tenk1 group by length(stringu1);
|
||||||
|
QUERY PLAN
|
||||||
|
---------------------------------------------------
|
||||||
|
Finalize HashAggregate
|
||||||
|
Group Key: (length((stringu1)::text))
|
||||||
|
-> Gather
|
||||||
|
Workers Planned: 4
|
||||||
|
-> Partial HashAggregate
|
||||||
|
Group Key: length((stringu1)::text)
|
||||||
|
-> Parallel Seq Scan on tenk1
|
||||||
|
(7 rows)
|
||||||
|
|
||||||
|
select length(stringu1) into parallel_write
|
||||||
|
from tenk1 group by length(stringu1);
|
||||||
|
drop table parallel_write;
|
||||||
|
explain (costs off) create materialized view parallel_mat_view as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
QUERY PLAN
|
||||||
|
---------------------------------------------------
|
||||||
|
Finalize HashAggregate
|
||||||
|
Group Key: (length((stringu1)::text))
|
||||||
|
-> Gather
|
||||||
|
Workers Planned: 4
|
||||||
|
-> Partial HashAggregate
|
||||||
|
Group Key: length((stringu1)::text)
|
||||||
|
-> Parallel Seq Scan on tenk1
|
||||||
|
(7 rows)
|
||||||
|
|
||||||
|
create materialized view parallel_mat_view as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
drop materialized view parallel_mat_view;
|
||||||
|
prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
explain (costs off) create table parallel_write as execute prep_stmt;
|
||||||
|
QUERY PLAN
|
||||||
|
---------------------------------------------------
|
||||||
|
Finalize HashAggregate
|
||||||
|
Group Key: (length((stringu1)::text))
|
||||||
|
-> Gather
|
||||||
|
Workers Planned: 4
|
||||||
|
-> Partial HashAggregate
|
||||||
|
Group Key: length((stringu1)::text)
|
||||||
|
-> Parallel Seq Scan on tenk1
|
||||||
|
(7 rows)
|
||||||
|
|
||||||
|
create table parallel_write as execute prep_stmt;
|
||||||
|
drop table parallel_write;
|
||||||
|
rollback;
|
@ -96,6 +96,7 @@ test: rules psql_crosstab amutils
|
|||||||
|
|
||||||
# run by itself so it can run parallel workers
|
# run by itself so it can run parallel workers
|
||||||
test: select_parallel
|
test: select_parallel
|
||||||
|
test: write_parallel
|
||||||
|
|
||||||
# no relation related tests can be put in this group
|
# no relation related tests can be put in this group
|
||||||
test: publication subscription
|
test: publication subscription
|
||||||
|
@ -134,6 +134,7 @@ test: stats_ext
|
|||||||
test: rules
|
test: rules
|
||||||
test: psql_crosstab
|
test: psql_crosstab
|
||||||
test: select_parallel
|
test: select_parallel
|
||||||
|
test: write_parallel
|
||||||
test: publication
|
test: publication
|
||||||
test: subscription
|
test: subscription
|
||||||
test: amutils
|
test: amutils
|
||||||
|
42
src/test/regress/sql/write_parallel.sql
Normal file
42
src/test/regress/sql/write_parallel.sql
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
--
|
||||||
|
-- PARALLEL
|
||||||
|
--
|
||||||
|
|
||||||
|
-- Serializable isolation would disable parallel query, so explicitly use an
|
||||||
|
-- arbitrary other level.
|
||||||
|
begin isolation level repeatable read;
|
||||||
|
|
||||||
|
-- encourage use of parallel plans
|
||||||
|
set parallel_setup_cost=0;
|
||||||
|
set parallel_tuple_cost=0;
|
||||||
|
set min_parallel_table_scan_size=0;
|
||||||
|
set max_parallel_workers_per_gather=4;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Test write operations that has an underlying query that is eligble
|
||||||
|
-- for parallel plans
|
||||||
|
--
|
||||||
|
explain (costs off) create table parallel_write as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
create table parallel_write as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
drop table parallel_write;
|
||||||
|
|
||||||
|
explain (costs off) select length(stringu1) into parallel_write
|
||||||
|
from tenk1 group by length(stringu1);
|
||||||
|
select length(stringu1) into parallel_write
|
||||||
|
from tenk1 group by length(stringu1);
|
||||||
|
drop table parallel_write;
|
||||||
|
|
||||||
|
explain (costs off) create materialized view parallel_mat_view as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
create materialized view parallel_mat_view as
|
||||||
|
select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
drop materialized view parallel_mat_view;
|
||||||
|
|
||||||
|
prepare prep_stmt as select length(stringu1) from tenk1 group by length(stringu1);
|
||||||
|
explain (costs off) create table parallel_write as execute prep_stmt;
|
||||||
|
create table parallel_write as execute prep_stmt;
|
||||||
|
drop table parallel_write;
|
||||||
|
|
||||||
|
rollback;
|
Reference in New Issue
Block a user