背景
当已有数据库表不满足应用需求时,我们可以将表删除并重建它。但如果表中已经存有数据或者被其他数据库对象(外键约束)引用,这种做法就显得很不方便。因此,PostgreSQL提供了一系列的命令来对已有表的结构做如下修改:
增加列
移除列
增加约束
移除约束
修改默认值
修改列的数据类型
接下来会通过一些例子对相关命令进行分析。
增加列
增加列的SQL语句如下,新列将被默认值所填充(如果没有指定DEFAULT子句,则会填充空值)。
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name ADD [ COLUMN ] [ IF NOT EXISTS ] column_name data_type [ COLLATE collation ] [ column_constraint [ ... ] ]在PG11版本之前,增加带有默认值的列,会重写整个表。重写表会对表加Access Exclusive锁,期间整张表是无法访问的。
从PG11版本开始,在系统表 pg_catalog.pg_attribute 中添加了两个字段:atthasmissing 和 attmissingval。新增列的默认值(volatile除外)会记录到attmissingval,并且对应的atthasmissing会被设置为true。查询时如果tuple中不包含对应的列,则会返回attmissingval的值。
postgres=# create table t(a int, b varchar(10));
postgres=# insert into t select i, i from generate_series(1, 10000000) i;
postgres=# \timing on
postgres=# alter table t add column c int default 1;
Time: 0.496 ms
postgres=# alter table t add column d int;
Time: 0.493 ms
postgres=# select attname, attmissingval, atthasmissing FROM pg_attribute WHERE attnum > 0 and attrelid = 't'::regclass;
attname | attmissingval | atthasmissing
---------+---------------+---------------
a | | f
b | | f
c | {1} | t
d | | f
(4 rows)一旦表被重写(例如执行 VACUUM FULL table 操作),相应的 atthasmissing 和 attmissingval 属性将会被清除,因为系统不再需要这些值。
postgres=# vacuum full t;
VACUUM
Time: 2933.191 ms (00:02.933)
postgres=# select attname, attmissingval, atthasmissing FROM pg_attribute WHERE attnum > 0 and attrelid = 't'::regclass;
attname | attmissingval | atthasmissing
---------+---------------+---------------
a | | f
b | | f
c | | f
d | | f
(4 rows)移除列
移除列的命令如下,移除一个列后其上的索引、约束也会自动移除。但是如果该列被另一个表的外键所引用,PostgreSQL不会移动地移除该约束,这时需要使用CASCADE来移除任何依赖于被删除列的所有对象。
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name DROP [ COLUMN ] [ IF EXISTS ] column_name [ RESTRICT | CASCADE ]移除一个列时,并不会重建表(逐行扫表重写),而是将pg_attribute中对应的列的attname字段修改为pg.dropped.idx,attisdropped标记为true,查询时会跳过该列。因此,移除列操作可以很快完成。
postgres=# create table t(a int, b varchar(10));
postgres=# alter table t add check(b <> '');
postgres=# create index on t(b);
postgres=# insert into t select i, i from generate_series(1, 10000000) i;
postgres=# \d+ t
Table "public.t"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+-----------------------+-----------+----------+---------+----------+-------------+--------------+-------------
a | integer | | | | plain | | |
b | character varying(10) | | | | extended | | |
Indexes:
"t_b_idx" btree (b)
Check constraints:
"t_b_check" CHECK (b::text <> ''::text)
Access method: heap
postgres=# \timing on
postgres=# alter table t drop column b;
Time: 50.658 ms
postgres=# \d+ t
Table "public.t"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+---------+---------+-------------+--------------+-------------
a | integer | | | | plain | | |
Access method: heap
postgres=# select attname, attisdropped FROM pg_attribute WHERE attnum > 0 and attrelid = 't'::regclass;
attname | attisdropped
------------------------------+--------------
a | f
........pg.dropped.2........ | t
(2 rows)与添加列不同,vacuum full后,移除的列并不会从系统表pg_attribute中删除。
postgres=# vacuum full t;
Time: 2334.762 ms (00:02.335)
postgres=# select attname, attisdropped FROM pg_attribute WHERE attnum > 0 and attrelid = 't'::regclass;
attname | attisdropped
------------------------------+--------------
a | f
........pg.dropped.2........ | t
(2 rows)添加约束
为一个表添加约束的命令如下:
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name ADD table_constraint [ CONSTRAINT constraint_name ]
{ CHECK ( expression ) [ NO INHERIT ] |
UNIQUE ( column_name [, ... ] ) index_parameters |
PRIMARY KEY ( column_name [, ... ] ) index_parameters |
EXCLUDE [ USING index_method ] ( exclude_element WITH operator [, ... ] ) index_parameters [ WHERE ( predicate ) ] |
FOREIGN KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ... ] ) ]
[ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON UPDATE action ] }
[ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
[ NOT VALID ]例如:
-- 列b不能为空串''
alter table t add check (b <> '');
-- 列a值必须唯一
alter table t add constraint a_unique unique(a);
-- 列b不能为NULL
alter table t alter column b set not null;需要注意的是:添加约束时会检查表中的数据是否满足约束,不满足添加约束会报错。
postgres=# create table t(a int, b varchar(10));
postgres=# insert into t select i, i from generate_series(1, 10000000) i;
postgres=# insert into t(b) values ('1');
postgres=# \timing on
Timing is on.
postgres=# alter table t add check (b <> '');
Time: 611.432 ms
postgres=# alter table t add constraint b_unique unique(b);
ERROR: could not create unique index "b_unique"
DETAIL: Key (b)=(1) is duplicated.
Time: 573.418 ms移除约束
移除约束的命令如下,执行命令时必须指定约束的名称。如果约束被其他对象依赖,则需要加上CASCADE。
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name DROP CONSTRAINT [ IF EXISTS ] constraint_name [ RESTRICT | CASCADE ]移除约束不用扫表做检查,因此移除一个约束可以很快完成。
修改列的默认值
修改列的默认值的命令如下,设置或删除列的默认值都不会影响表中已经存在的行,只会改变后续INSERT插入行的默认值,因此该命令可以很快执行完成。
-- 设置默认值
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name ALTER [ COLUMN ] column_name SET DEFAULT expression
-- 删除默认值
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name ALTER [ COLUMN ] column_name DROP DEFAULT列的默认值存在information_schema.columns的column_default列中,未定义默认值的列被隐式地设置为空值,也存在information_schema.columns。
postgres=# create table t(a int, b varchar(10));
postgres=# alter table t alter column b set default '1';
postgres=# select column_name, column_default from information_schema.columns where table_name = 't';
column_name | column_default
-------------+------------------------
a |
b | '1'::character varying
(2 rows)
postgres=# alter table t alter column b drop default ;
postgres=# select column_name, column_default from information_schema.columns where table_name = 't';
column_name | column_default
-------------+----------------
a |
b |
(2 rows)修改列的数据类型
以下命令可以将一个列的数据类型转换为另一种数据类型。只有当列的数据能够通过隐式转换成目标类型时才能成功,否则要使用USING子句指定如何将当前的数据转为目标类型。
ALTEL TABLE [ IF EXISTS ] [ ONLY ] name ALTER [ COLUMN ] column_name [ SET DATA ] TYPE data_type [ COLLATE collation ] [ USING expression ]需要注意的是,转换后的数据可能会使该列违反现有约束而失败。
将列的类型由大变小(由高精度变为低精度),表及索引都需要重写,并且整个过程都会对表加Access Exclusive锁,会对业务产生较大影响。此外列的类型由大变小还可能导致数据发生错误(截断)。
postgres=# create table t(a bigint, b varchar(10));
postgres=# insert into t select i, i from generate_series(1, 10000000) i;
Time: 7558.935 ms (00:07.559)
postgres=# alter table t alter column a type int;
Time: 5213.292 ms (00:05.213)
postgres=# alter table t alter column b type varchar(9);
Time: 5667.193 ms (00:05.667)将列的类型由小变大(由低精度变为高精度),表数据不需要重写(int变为bigint除外)。
postgres=# create table t(a int, b varchar(10), c float);
postgres=# insert into t select i, i, i from generate_series(1, 10000000) i;
Time: 7818.867 ms (00:07.819)
postgres=# alter table t alter column a type bigint;
Time: 5527.618 ms (00:05.528)
postgres=# alter table t alter column b type varchar(20);
Time: 0.438 ms
postgres=# alter table t alter column c type float8;
Time: 0.409 ms将列的类型由小变大(由低精度变为高精度),与该列相关的索引是否需要重建取决于表类型:普通表的索引不需要重建,分区表的索引会自动重建(子表不会重写,子表的索引会重建)。因此分区表不要轻易修改列的类型。
postgres=# create table t(a int, b varchar(10));
postgres=# create index on t(b);
postgres=# insert into t select i, i from generate_series(1, 10000000) i;
Time: 66763.479 ms (01:06.763)
postgres=# alter table t alter column b type varchar(20);
Time: 0.884 ms
postgres=# create table t_p(a int, b varchar(10)) PARTITION BY HASH (a);
postgres=# create table t_p1 PARTITION OF t_p FOR VALUES WITH (MODULUS 2, REMAINDER 0);
postgres=# create table t_p2 PARTITION OF t_p FOR VALUES WITH (MODULUS 2, REMAINDER 1);
postgres=# create index on t_p(b);
postgres=# insert into t_p select i, i from generate_series(1, 10000000) i;
Time: 8463.736 ms (00:08.464)
postgres=# alter table t_p alter column b type varchar(40);
Time: 16118.603 ms (00:16.119)
postgres=#总结
本文结合具体的例子介绍了“增加列”、“移除列”、“添加约束”、“移除约束”、“修改列的默认值”、“修改列的数据类型”这几种常用的修改表命令及其执行效率:
从PG11版本开始,新增列都不需要重写表,执行速度很快。
移除列只需要修改系统表数据,不需要重写表,执行速度很快。移除列也会自动移除相关索引和约束。
添加约束需要检查表中数据是否满足新增约束,执行速度较慢。
移除约束不需要重写表,执行速度很快。
修改列的默认值不会影响现有数据,只需修改系统表,执行速度很快。
修改列的数据类型分为多种情况:
由大变小(高精度到低精度)表和索引都需要重写,执行速度很慢。
由小变大(低精度到高精度,int到bigint除外),表数据不需要重写,执行速度较快。
由小变大,普通表的索引不需要重建,分区表的索引需要重建,执行速度很慢。
后续章节将结合源码,以修改列的数据类型进一步分析PostgreSQL修改表操作的流程。




