PostgreSQL10.0內建分割槽表

德哥發表於2016-12-21

標籤

PostgreSQL , 10.0 , 分割槽表 , partitiion table , range , list


背景

PostgreSQL 和它的LOGO大象一樣,給人非常強大的安全感。

就拿它的Feature來說,一個大的feature要打磨很多年才能正式的合併到master分支。

比如平行計算的特性,從9.4就開始準備,加入了work process和dynamic shared memory的功能,奠定了多程式並行執行的基礎。

一致到9.6,經歷了3年的開發,大量的測試,終於RELEASE了。

今天要說一說它的分割槽表,經歷了幾年的醞釀,終於在10.0加入到master了。(PG的企業版本EDB很早就支援分割槽表了)

如果你現在需要這個功能,可以使用postgrespro的外掛, pg_pathman,否則就等10.0吧,明年9月份左右release。

《PostgreSQL 9.5+ 高效分割槽表實現 – pg_pathman》

PostgreSQL 10.0 分割槽表的設計

PostgreSQL的分割槽表依舊使用了繼承的特性,好訊息是, 終於不需要手工寫規則了。

在使用方面,我們需要先建立主表,然後建立分割槽(即子表)。老頑固啊,就是要讓你記住PG是有繼承的概念的嗎?

目前支援range、list分割槽(10.0 release時應該會支援更多的方法),分割槽列的型別必須支援btree索引介面(幾乎涵蓋所有型別, 後面會說到檢查方法)。

語法

1. 建立主表

[ PARTITION BY { RANGE | LIST } ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [, ... ] ) ]  

2. 建立分割槽

PARTITION OF parent_table [ (  
  { column_name [ column_constraint [ ... ] ]  
    | table_constraint }  
    [, ... ]  
) ] FOR VALUES partition_bound_spec  

and partition_bound_spec is:  

{ IN ( expression [, ...] )   -- list分割槽  
  |  
  FROM ( { expression | UNBOUNDED } [, ...] ) TO ( { expression | UNBOUNDED } [, ...] ) }  -- range分割槽, unbounded表示無限小或無限大  

語法解釋

1. 建立主表

partition by 指定分割槽表的型別range或list

指定分割槽列,或表示式作為分割槽鍵。

range分割槽表鍵:支援指定多列、或多表示式,支援混合(鍵,非表示式中的列,會自動新增not null的約束)

list分割槽表鍵:支援單個列、或單個表示式

分割槽鍵必須有對應的btree索引方法的ops(可以檢視系統表得到)

select typname from pg_type where oid in (select opcintype from pg_opclass);    

主表不會有任何資料,資料會根據分割槽規則進入對應的分割槽表

如果插入資料時,分割槽鍵的值沒有匹配的分割槽,會報錯

不支援全域性的unique, primary key, exclude, foreign key約束,只能在對應的分割槽建立這些約束

PARTITION BY { RANGE | LIST } ( { column_name | ( expression ) } [ opclass ] [, ...] )  

The optional PARTITION BY clause specifies a strategy of partitioning the table.   

The table thus created is called a partitioned table. The parenthesized list of columns or expressions forms the partition key for the table.   

When using range partitioning, the partition key can include multiple columns or expressions, but for list partitioning, the partition key must consist of a single column or expression.   

If no btree operator class is specified when creating a partitioned table, the default btree operator class for the datatype will be used. If there is none, an error will be reported.   

A partitioned table is divided into sub-tables (called partitions), which are created using separate CREATE TABLE commands.   

The partitioned table is itself empty. A data row inserted into the table is routed to a partition based on the value of columns or expressions in the partition key.   

If no existing partition matches the values in the new row, an error will be reported.  

Partitioned tables do not support UNIQUE, PRIMARY KEY, EXCLUDE, or FOREIGN KEY constraints;   

however, you can define these constraints on individual partitions.  

When using range partitioning, a NOT NULL constraint is added to each non-expression column in the partition key.  

2. 建立分割槽

主表建立好之後,需要再建立分割槽(為了體現繼承麼?就不能一次幹掉?)

建立分割槽表需要指定它的主表是哪個?

指定分割槽鍵的屬性(範圍,LIST值),範圍,LIST不能有重疊(這個很好理解,否則重疊部分的資料到底插到哪個分割槽去呢?)

分割槽表和主表的 列數量,定義 必須完全一致,(包括OID也必須一致,要麼都有,要麼都沒有)

可以為分割槽表的列單獨增加Default值,或約束。

使用者還可以對分割槽表增加表級約束

如果新增的分割槽表check約束,名字與主表的約束名一致,則約束內容必須與主表一致

當使用者往主表插入資料庫時,記錄被自動路由到對應的分割槽,如果沒有合適的分割槽,則報錯

如果更新資料,並且更新後的KEY導致資料需要移動到另一分割槽,則會報錯,(意思是分割槽鍵可以更新,但是不支援更新後的資料移出到別的分割槽表)

修改主表的欄位名,欄位型別時,會自動同時修改所有的分割槽

TRUNCATE 主表時,會清除所有繼承表分割槽的記錄(如果有多級分割槽,則會一直清除到所有的直接和間接繼承的分割槽)

如果要清除單個分割槽,請對分割槽進行操作

如果要刪除分割槽表,可以使用DROP TABLE的DDL語句,注意這個操作會對主表也加access exclusive lock。

PARTITION OF parent_table FOR VALUES partition_bound_spec  

partition_bound_spec is:    

{ IN ( expression [, ...] ) |  
  FROM ( { expression | UNBOUNDED } [, ...] ) TO ( { expression | UNBOUNDED } [, ...] ) }  

Creates the table as partition of the specified parent table.  

The partition bound specification must correspond to the partitioning method and partition key of the parent table, and must not overlap with any existing partition of that parent.  

A partition cannot have columns other than those inherited from the parent. That includes the oid column, which can be specified using the WITH (OIDS) clause.   

Defaults and constraints can optionally be specified for each of the inherited columns.   

One can also specify table constraints in addition to those inherited from the parent.   

If a check constraint with the name matching one of the parent`s constraint is specified, it is merged with the latter, provided the specified condition is same.  

Rows inserted into a partitioned table will be automatically routed to the correct partition. If no suitable partition exists, an error will occur.   

Also, if updating a row in a given partition causes it to move to another partition due to the new partition key, an error will occur.  

A partition must have the same column names and types as the table of which it is a partition.   

Therefore, modifications to the column names or types of the partitioned table will automatically propagate to all children, as will operations such as TRUNCATE which normally affect a table and all of its inheritance children.   

It is also possible to TRUNCATE a partition individually, just as for an inheritance child.   

Note that dropping a partition with DROP TABLE requires taking an ACCESS EXCLUSIVE lock on the parent table.  

例子

測試版本編譯

wget https://git.postgresql.org/gitweb/?p=postgresql.git;a=snapshot;h=55caaaeba877eac1feb6481fb413fa04ae9046ac;sf=tgz  

tar -zxvf postgresql-55caaae.tar.gz  

cd postgresql-55caaae  

./configure --prefix=/home/digoal/pgsql10.0  

make world -j 32  

make install-world  

export PGPORT=5299  
export PGDATA=/disk1/pgdata/pg_root10  
export LANG=en_US.utf8  
export PGHOME=/home/digoal/pgsql10.0  
export LD_LIBRARY_PATH=$PGHOME/lib:/lib64:/usr/lib64:/usr/local/lib64:/lib:/usr/lib:/usr/local/lib:$LD_LIBRARY_PATH  
export DATE=`date +"%Y%m%d%H%M"`  
export PATH=$PGHOME/bin:$PATH:.  
export MANPATH=$PGHOME/share/man:$MANPATH  
export PGHOST=$PGDATA  
export PGUSER=postgres  
export PGDATABASE=postgres  


initdb -D $PGDATA -U postgres -E SQL_ASCII --locale=C  

vi $PGDATA/postgresql.conf  
cat postgresql.conf|grep "^[a-z]"|awk -F "#" `{print $1}`  

listen_addresses = `0.0.0.0`  
port = 5299  
max_connections = 500  
superuser_reserved_connections = 13  
unix_socket_directories = `.`  
shared_buffers = 16GB  
maintenance_work_mem = 1024MB  
dynamic_shared_memory_type = posix  
bgwriter_delay = 10ms  
bgwriter_lru_maxpages = 1000  
bgwriter_lru_multiplier = 5.0  
bgwriter_flush_after = 0  
max_parallel_workers_per_gather = 0  
max_parallel_workers = 0              
old_snapshot_threshold = -1  
backend_flush_after = 0  
wal_buffers = 512MB  
wal_writer_delay = 10ms  
wal_writer_flush_after = 0  
checkpoint_timeout = 55min  
max_wal_size = 128GB  
min_wal_size = 8GB  
checkpoint_completion_target = 0.5  
checkpoint_flush_after = 0  
random_page_cost = 1.0  
effective_cache_size = 400GB  
log_destination = `csvlog`  
logging_collector = on  
log_truncate_on_rotation = on  
log_checkpoints = on  
log_connections = on  
log_disconnections = on  
log_error_verbosity = verbose    
log_timezone = `PRC`  
log_autovacuum_min_duration = 0  
datestyle = `iso, mdy`  
timezone = `PRC`  
lc_messages = `C`  
lc_monetary = `C`  
lc_numeric = `C`  
lc_time = `C`  
default_text_search_config = `pg_catalog.english`  

pg_ctl start  

查詢可用的opclass

由於分割槽表的鍵值列必須支援btree索引,所以建議確認一下。

建立主表時,需要指定ops,如果沒有指定,會自動選擇型別預設的btree ops.

postgres=# select opcintype::regtype,opcname from pg_opclass ;  
          opcintype          |        opcname           
-----------------------------+------------------------  
 abstime                     | abstime_ops  
 anyarray                    | array_ops  
 anyarray                    | array_ops  
 bit                         | bit_ops  
 boolean                     | bool_ops  
 character                   | bpchar_ops  
 character                   | bpchar_ops  
 。。。。。。  

分割槽表

1. range 分割槽表

例子為按時間欄位分割槽的分割槽表,使用者在建立主表時,指定分割槽鍵,建立分割槽時,指定每個分割槽的範圍。

1.1 主表

range分割槽表,支援混合式的分割槽鍵(多列,多表示式,或者混合式)

和前面說的一樣,不能使用全域性PK

postgres=# create table t_range(id int primary key, info text, crt_time timestamp) partition by range (crt_time, mod(hashtext(info), 4));  
ERROR:  0A000: primary key constraints are not supported on partitioned tables  
LINE 1: create table t_range(id int primary key, info text, crt_time...  
                                    ^  
LOCATION:  transformColumnDefinition, parse_utilcmd.c:620  


postgres=# create table t_range(id int, info text, crt_time timestamp) partition by range (crt_time);  
CREATE TABLE  

1.2 分割槽

postgres=# create table t_range_0_201610 partition of t_range (id  primary key, info , crt_time ) for values from (`2016-10-01`) to (`2016-11-01`);  -- >= 20161001 and < 20161101  
CREATE TABLE  
postgres=# create table t_range_0_201611 partition of t_range (id  primary key, info , crt_time ) for values from (`2016-11-01`) to (`2016-12-01`);  -- >= 20161101 and < 20161201  
CREATE TABLE  

甚至你可以將分割槽表放到不同的schema下面

postgres=# create schema x;
CREATE SCHEMA

postgres=# create table x.t_range_0_201612 partition of t_range (id  primary key, info , crt_time ) for values from (`2016-12-01`) to (`2017-01-01`); 
CREATE TABLE

postgres=# d+ t_range
                                             Table "public.t_range"
  Column  |            Type             | Collation | Nullable | Default | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+---------+----------+--------------+-------------
 id       | integer                     |           |          |         | plain    |              | 
 info     | text                        |           |          |         | extended |              | 
 crt_time | timestamp without time zone |           | not null |         | plain    |              | 
Partition key: RANGE (crt_time)
Partitions: t_range_0_201610 FOR VALUES FROM (`2016-10-01 00:00:00`) TO (`2016-11-01 00:00:00`),
            t_range_0_201611 FOR VALUES FROM (`2016-11-01 00:00:00`) TO (`2016-12-01 00:00:00`),
            x.t_range_0_201612 FOR VALUES FROM (`2016-12-01 00:00:00`) TO (`2017-01-01 00:00:00`)

2. list 分割槽表

2.1 主表

postgres=# create table t_list(id int, info text, crt_time timestamp) partition by list ( mod(hashtext(info), 4) );  
CREATE TABLE  

2.2 分割槽

postgres=# create table t_list_0 partition of t_list (id  primary key, info , crt_time ) for values in (0);  
CREATE TABLE  
postgres=# create table t_list_1 partition of t_list (id  primary key, info , crt_time ) for values in (1);  
CREATE TABLE  
postgres=# create table t_list_2 partition of t_list (id  primary key, info , crt_time ) for values in (2);  
CREATE TABLE  
postgres=# create table t_list_3 partition of t_list (id  primary key, info , crt_time ) for values in (3);  
CREATE TABLE  

多級分割槽

建立主表

create table t_range_list(id int, info text, crt_time timestamp) partition by list ( mod(hashtext(info), 4) );  

建立一級分割槽,主表

create table t_range_list_0 partition of t_range_list (id  , info , crt_time ) for values in (0) partition by range (crt_time);  
create table t_range_list_1 partition of t_range_list (id  , info , crt_time ) for values in (1) partition by range (crt_time);  
create table t_range_list_2 partition of t_range_list (id  , info , crt_time ) for values in (2) partition by range (crt_time);  
create table t_range_list_3 partition of t_range_list (id  , info , crt_time ) for values in (3) partition by range (crt_time);  

建立2級分割槽表

create table t_range_list_0_201611 partition of t_range_list_0 (id  primary key, info , crt_time ) for values from (`2016-10-01`) to (`2016-11-01`);  
create table t_range_list_0_201612 partition of t_range_list_0 (id  primary key, info , crt_time ) for values from (`2016-11-01`) to (`2016-12-01`);  

create table t_range_list_1_201611 partition of t_range_list_1 (id  primary key, info , crt_time ) for values from (`2016-10-01`) to (`2016-11-01`);  
create table t_range_list_1_201612 partition of t_range_list_1 (id  primary key, info , crt_time ) for values from (`2016-11-01`) to (`2016-12-01`);  

create table t_range_list_2_201611 partition of t_range_list_2 (id  primary key, info , crt_time ) for values from (`2016-10-01`) to (`2016-11-01`);  
create table t_range_list_2_201612 partition of t_range_list_2 (id  primary key, info , crt_time ) for values from (`2016-11-01`) to (`2016-12-01`);  

create table t_range_list_3_201611 partition of t_range_list_3 (id  primary key, info , crt_time ) for values from (`2016-10-01`) to (`2016-11-01`);  
create table t_range_list_3_201612 partition of t_range_list_3 (id  primary key, info , crt_time ) for values from (`2016-11-01`) to (`2016-12-01`);  

執行計劃

1. 分割槽鍵條件建議使用同型別的常量、如果是函式則必須使用返回值與分割槽鍵型別一致,stable或immutable的函式。

postgres=# explain select * from t_range where crt_time=now()::timestamp;  -- 如果需要轉換,轉換函式必須為immutable(常量),這樣可以在進行邏輯推理前被呼叫  
                                QUERY PLAN                                 
---------------------------------------------------------------------------
 Append  (cost=0.00..23929.55 rows=14 width=42)
   ->  Seq Scan on t_range  (cost=0.00..0.00 rows=1 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201610  (cost=0.00..23870.00 rows=1 width=17)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201611  (cost=0.00..29.78 rows=6 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201612  (cost=0.00..29.78 rows=6 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
(9 rows)
postgres=# create or replace function sysdate() returns timestamp as $$
  select now()::timestamp ;
$$ language sql strict immutable;
CREATE FUNCTION

postgres=# explain select * from t_range where crt_time=sysdate();
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Append  (cost=0.00..24.12 rows=7 width=44)
   ->  Seq Scan on t_range  (cost=0.00..0.00 rows=1 width=44)
         Filter: (crt_time = `2016-12-16 09:58:03.246322`::timestamp without time zone)
   ->  Seq Scan on t_range_0_201612  (cost=0.00..24.12 rows=6 width=44)
         Filter: (crt_time = `2016-12-16 09:58:03.246322`::timestamp without time zone)
(5 rows)

返回型別一致的C程式碼stable函式也可以被排除,否則會被解釋

postgres=# create or replace function sysdate() returns timestamp as $$
  select now()::timestamp ;
$$ language sql strict stable;
CREATE FUNCTION

postgres=# explain select * from t_range where crt_time=sysdate();
                                QUERY PLAN                                 
---------------------------------------------------------------------------
 Append  (cost=0.00..23929.55 rows=14 width=42)
   ->  Seq Scan on t_range  (cost=0.00..0.00 rows=1 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201610  (cost=0.00..23870.00 rows=1 width=17)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201611  (cost=0.00..29.78 rows=6 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
   ->  Seq Scan on t_range_0_201612  (cost=0.00..29.78 rows=6 width=44)
         Filter: (crt_time = (now())::timestamp without time zone)
(9 rows)

返回型別一致的C程式碼stable函式也可以被排除

postgres=# create table p(id int, info text, crt_time timestamptz);
CREATE TABLE
postgres=# create table t(like p) inherits(p);
NOTICE:  merging column "id" with inherited definition
NOTICE:  merging column "info" with inherited definition
NOTICE:  merging column "crt_time" with inherited definition
CREATE TABLE
postgres=# create table p1(like p) inherits(p);
NOTICE:  merging column "id" with inherited definition
NOTICE:  merging column "info" with inherited definition
NOTICE:  merging column "crt_time" with inherited definition
CREATE TABLE
postgres=# create table p2(like p) inherits(p);
NOTICE:  merging column "id" with inherited definition
NOTICE:  merging column "info" with inherited definition
NOTICE:  merging column "crt_time" with inherited definition
CREATE TABLE
postgres=# alter table t add constraint ck check(crt_time >=`2016-11-01` and crt_time <`2016-12-01`);
ALTER TABLE
postgres=# alter table p1 add constraint ck check(crt_time >=`2016-10-01` and crt_time <`2016-11-01`);
ALTER TABLE
postgres=# alter table p2 add constraint ck check(crt_time >=`2016-09-01` and crt_time <`2016-10-01`);
ALTER TABLE
postgres=# explain select * from p where crt_time=now();
                                       QUERY PLAN                                       
----------------------------------------------------------------------------------------
 Append  (cost=0.00..0.00 rows=1 width=44)
   ->  Seq Scan on p  (cost=0.00..0.00 rows=1 width=44)
         Filter: (crt_time = `2016-12-16 10:02:40.483355+08`::timestamp with time zone)
(3 rows)

繫結分割槽,解綁分割槽

ALTER TABLE可以將表繫結到分割槽表的某個分割槽,也可以將某個已有分割槽從分割槽表剔除。

ALTER TABLE [ IF EXISTS ] name
    ATTACH PARTITION partition_name FOR VALUES partition_bound_spec
ALTER TABLE [ IF EXISTS ] name
    DETACH PARTITION partition_name

解釋

繫結時,需要指定分割槽鍵的邊界(範圍、或LIST值)

列名與主表一致、列型別一致、列順序一致、NOT NULL、CHECK約束一致、不建議UNIQUE,PK,FK。

不能有任何主表非繼承的CHECK約束,(建議對約束重建,不要加not inherit屬性)

加入的分割槽需要進行全表掃描,確認所有的記錄都在指定的分割槽鍵邊界內。

如果要避免全表掃描,建議在執行繫結前,對這個表加入CHECK約束,確保約束可以保證記錄都在邊界內。 但是這種方法不適用於分割槽鍵包含表示式並且分割槽不允許NULL值的情況。

ATTACH PARTITION partition_name FOR VALUES partition_bound_spec
This form attaches an existing table (which might itself be partitioned) as a partition of the target table using the same syntax for partition_bound_spec as CREATE TABLE. 

The partition bound specification must correspond to the partitioning strategy and partition key of the target table. 

The table to be attached must have all the same columns as the target table and no more; moreover, the column types must also match. 

Also, it must have all the NOT NULL and CHECK constraints of the target table. Currently UNIQUE, PRIMARY KEY, and FOREIGN KEY constraints are not considered. 

If any of the CHECK constraints of the table being attached is marked NO INHERIT, the command will fail; such a constraint must be recreated without the NO INHERIT clause.

A full table scan is performed on the table being attached to check that no existing row in the table violates the partition constraint. 

It is possible to avoid this scan by adding a valid CHECK constraint to the table that would allow only the rows satisfying the desired partition constraint before running this command. 

It will be determined using such a constraint that the table need not be scanned to validate the partition constraint. 

This does not work, however, if any of the partition keys is an expression and the partition does not accept NULL values. 

If attaching a list partition that will not accept NULL values, also add NOT NULL constraint to the partition key column, unless it`s an expression.
DETACH PARTITION partition_name
This form detaches specified partition of the target table. The detached partition continues to exist as a standalone table, but no longer has any ties to the table from which it was detached.

All the actions except RENAME, SET TABLESPACE, SET SCHEMA, ATTACH PARTITION, and DETACH PARTITION can be combined into a list of multiple alterations to apply in parallel. 

For example, it is possible to add several columns and/or alter the type of several columns in a single command. This is particularly useful with large tables, since only one pass over the table need be made.

You must own the table to use ALTER TABLE. To change the schema or tablespace of a table, you must also have CREATE privilege on the new schema or tablespace. 

To add the table as a new child of a parent table, you must own the parent table as well. Also, to attach a table as a new partition of the table, you must own the table being attached. 

To alter the owner, you must also be a direct or indirect member of the new owning role, and that role must have CREATE privilege on the table`s schema. 

(These restrictions enforce that altering the owner doesn`t do anything you couldn`t do by dropping and recreating the table. 

However, a superuser can alter ownership of any table anyway.) To add a column or alter a column type or use the OF clause, you must also have USAGE privilege on the data type.

效能測試

range 分割槽表

插入

postgres=# insert into t_range select generate_series(1,1000000),`test`,`2016-10-01`;  
INSERT 0 1000000  
Time: 3849.514 ms (00:03.850)  

直接插分割槽表  
postgres=# truncate t_range;  
TRUNCATE TABLE  
Time: 72.181 ms  
postgres=# insert into t_range_0_201610 select generate_series(1,1000000),`test`,`2016-10-01`;  
INSERT 0 1000000  
Time: 3587.772 ms (00:03.588)  

查詢,主表未消除

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from t_range where id=1 and crt_time=`2016-10-01`;  
                                                                      QUERY PLAN                                                                        
------------------------------------------------------------------------------------------------------------------------------------------------------  
 Append  (cost=0.00..2.45 rows=2 width=30) (actual time=0.037..0.038 rows=1 loops=1)  
   Buffers: shared hit=4  
   ->  Seq Scan on public.t_range  (cost=0.00..0.00 rows=1 width=44) (actual time=0.020..0.020 rows=0 loops=1)  
         Output: t_range.id, t_range.info, t_range.crt_time  
         Filter: ((t_range.id = 1) AND (t_range.crt_time = `2016-10-01 00:00:00`::timestamp without time zone))  
   ->  Index Scan using t_range_0_201610_pkey on public.t_range_0_201610  (cost=0.42..2.45 rows=1 width=17) (actual time=0.016..0.016 rows=1 loops=1)  
         Output: t_range_0_201610.id, t_range_0_201610.info, t_range_0_201610.crt_time  
         Index Cond: (t_range_0_201610.id = 1)  
         Filter: (t_range_0_201610.crt_time = `2016-10-01 00:00:00`::timestamp without time zone)  
         Buffers: shared hit=4  
 Planning time: 0.248 ms  
 Execution time: 0.069 ms  
(12 rows)  

on conflict do

postgres=# insert into t_range select generate_series(1,1000000),`test`,`2016-10-01` on conflict do nothing;  
ERROR:  0A000: ON CONFLICT clause is not supported with partitioned tables  
LOCATION:  transformInsertStmt, analyze.c:825  
Time: 0.350 ms  

參考

1. https://www.postgresql.org/docs/devel/static/sql-createtable.html

《PostgreSQL 9.5+ 高效分割槽表實現 – pg_pathman》

《PostgreSQL 優化器邏輯推理能力 原始碼解析》


相關文章