PostgreSQL 11 新特性之快速增加包含非空默認值的列
作者: 不剪發(fā)的Tony老師
畢業(yè)于北京航空航天大學(xué),十多年數(shù)據(jù)庫管理與開發(fā)經(jīng)驗,目前在一家全球性的金融公司從事數(shù)據(jù)庫架構(gòu)設(shè)計。CSDN學(xué)院簽約講師以及GitChat專欄作者。csdn上的博客收藏于以下地址:https://tonydong.blog.csdn.net
文章目錄
在 PostgreSQL 11 之前,為表增加一個包含非空默認值的字段,將會導(dǎo)致表數(shù)據(jù)的重新寫入,為每一行添加該字段,并且填充默認值。如果針對大表執(zhí)行這種操作,將會非常耗時。
以下是一個 PostgreSQL 10 中添加字段(包含非空默認值)的示例:
-- PostgreSQL 10.5
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT);
INSERT INTO t1 (c1, c2)
SELECT seq, 'test'|| seq::TEXT
from generate_series ( 1, 1000000 ) seq;
\timing on
ALTER TABLE t1 ADD COLUMN c3 text DEFAULT 'abc';
ALTER TABLE
Time: 1218.268 ms (00:01.218)
在我們的環(huán)境中,為表 t1 (一百萬行數(shù)據(jù))添加字段 c3 大約需要 1.2 s 的時間。對于千萬級別甚至上億級別的表,添加這種字段將會非常緩慢。
接下來使用 PostgreSQL 11 執(zhí)行同樣的操作:
-- PostgreSQL 11.0
CREATE TABLE t1 (c1 INT PRIMARY KEY, c2 TEXT);
INSERT INTO t1 (c1, c2)
SELECT seq, 'test'|| seq::TEXT
from generate_series ( 1, 1000000 ) seq;
\timing on
ALTER TABLE t1 ADD COLUMN c3 text DEFAULT 'abc';
ALTER TABLE
Time: 7.392 ms
可以看到,PostgreSQL 11 中添加字段只需要 7 ms 的時間。如果數(shù)據(jù)量更大,性能提升就更明顯。
這個改進的原理就是在表 pg_catalog.pg_attribute 中添加兩個字段:atthasmissing 和 attmissingval。
\d pg_catalog.pg_attribute
Table "pg_catalog.pg_attribute"
Column | Type | Collation | Nullable | Default
---------------+-----------+-----------+----------+---------
attrelid | oid | | not null |
attname | name | | not null |
atttypid | oid | | not null |
attstattarget | integer | | not null |
attlen | smallint | | not null |
attnum | smallint | | not null |
attndims | integer | | not null |
attcacheoff | integer | | not null |
atttypmod | integer | | not null |
attbyval | boolean | | not null |
attstorage | "char" | | not null |
attalign | "char" | | not null |
attnotnull | boolean | | not null |
atthasdef | boolean | | not null |
atthasmissing | boolean | | not null |
attidentity | "char" | | not null |
attisdropped | boolean | | not null |
attislocal | boolean | | not null |
attinhcount | integer | | not null |
attcollation | oid | | not null |
attacl | aclitem[] | | |
attoptions | text[] | | |
attfdwoptions | text[] | | |
attmissingval | anyarray | | |
Indexes:
"pg_attribute_relid_attnam_index" UNIQUE, btree (attrelid, attname)
"pg_attribute_relid_attnum_index" UNIQUE, btree (attrelid, attnum)
使用 ALTER TABLE 語句添加字段的時候,如果指定了(非 VOLATILE)默認值,PostgreSQL 將該值存儲到 pg_attribute 表對應(yīng)行的 attmissingval 列中,并且將 atthasmissing 的值設(shè)置為 true。因此不需要重寫表。
SELECT attmissingval, atthasmissing
FROM pg_attribute
WHERE attrelid = 't1'::regclass
AND attname = 'c3';
attmissingval | atthasmissing
---------------+---------------
{abc} | t
(1 row)
新增字段的默認值不一定需要是一個靜態(tài)的表達式。它可以是任何非易變的表達式,例如 CURRENT_TIMESTAMP。但是,易變表達式(例如 random())仍然會導(dǎo)致表的重寫。對于表中已經(jīng)存在的行,查詢時直接返回 attmissingval 屬性的值。插入新的數(shù)據(jù)行(包括更新已有的行)時,使用用戶提供的值或者默認值進行填充,查詢時不需要使用 attmissingval。
SELECT * FROM t1 WHERE c1 = 1;
c1 | c2 | c3
----+-------+-----
1 | test1 | abc
(1 row)
INSERT INTO t1(c1, c2, c3) VALUES (0, 'zero', NULL);
SELECT * FROM t1 WHERE c1 = 0;
c1 | c2 | c3
----+------+----
0 | zero |
(1 row)
一旦該表被重寫(例如執(zhí)行 VACUUM FULL table 操作),相應(yīng)的 atthasmissing 和 attmissingval 屬性將會被清除,因為系統(tǒng)不再需要這些值。
VACUUM FULL t1;
VACUUM
Time: 2222.956 ms (00:02.223)
SELECT attmissingval, atthasmissing
FROM pg_attribute
WHERE attrelid = 't1'::regclass
AND attname = 'c3';
attmissingval | atthasmissing
---------------+---------------
| f
(1 row)