暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

在PostgreSQL中你真的需要varchar(n)吗?

原创 Lily 2022-10-21
2447

PostgreSQL自带三种存储字符的数据类型。char,varchar和text。
我们经常看到的是,当客户想要存储字符时使用varchar(n),并限制允许存储到字段/列的字符的数量。
在我们进行更深入的细节前:你唯一想用char或者varchar的原因是,二者的定义都符合SQL标准。
至于其他的原因,你应该用text。
让我们从三张示例表开始,表中包含一个char字段,一个varchar字段和text字段:

postgres=# create table t1 ( a char(2000) );
CREATE TABLE
postgres=# \d t1
                     Table "public.t1"
 Column |      Type       | Collation | Nullable | Default
--------+-----------------+-----------+----------+---------
 a      | character(2000) |           |          | 
postgres=# create table t2 ( a varchar(2000) );
CREATE TABLE
postgres=# \d t2
                         Table "public.t2"
 Column |          Type           | Collation | Nullable | Default
--------+-------------------------+-----------+----------+---------
 a      | character varying(2000) |           |          | 
 
postgres=# create table t3 ( a text );
CREATE TABLE
postgres=# \d t3
               Table "public.t3"
 Column | Type | Collation | Nullable | Default
--------+------+-----------+----------+---------
 a      | text |           |          | 
 
postgres=# 

表t1和t2可以存储字符串的上限是长度2000个的字符(非字节)。表t3可以存储任意字符串上限至1GB。我们将添加1000000行来看看这些数据的第一个不同点,如下:

postgres=# \timing on
Timing is on.
postgres=# insert into t1 select lpad(i::char,100,'0') from generate_series(1,1000000) i; 
INSERT 0 1000000
Time: 42041.031 ms (00:42.041)
postgres=# insert into t2 select lpad(i::varchar,100,'0') from generate_series(1,1000000) i; 
INSERT 0 1000000
Time: 2813.176 ms (00:02.813)
postgres=# insert into t3 select lpad(i::text,100,'0') from generate_series(1,1000000) i; 
INSERT 0 1000000
Time: 2790.226 ms (00:02.790)
postgres=# 

你会注意到,插入数据进包含char字段的表t1慢了很多。在varchar(n)和text表之间并没有太多差别。问题是:为什么char(n)表比其他两个表慢如此多呢?如果你比对两个表的大小,原因非常明显。

postgres=# select pg_size_pretty(pg_relation_size('t1'));
 pg_size_pretty 
----------------
 1953 MB
(1 row)
 
Time: 1.719 ms
postgres=# select pg_size_pretty(pg_relation_size('t2'));
 pg_size_pretty 
----------------
 128 MB
(1 row)
 
Time: 0.682 ms
postgres=# select pg_size_pretty(pg_relation_size('t3'));
 pg_size_pretty 
----------------
 128 MB
(1 row)
 
Time: 0.629 ms

第一个包含char(n)字段的表差不多2GB,而其他两个大小只有128MB,这是非常大的一个差别。原因是,char(n)以空白补齐并且总是消耗2000个字符大小,而varchar(n)和text是动态的。动态意味着它们只消耗存储字符串所需的空间,而不是尽可能多的空间。
这已经取消了char(n)的资格。
varchar(n)和text呢?二者在磁盘上的space消耗并没有不同。两者都用了需求所需的space去存储字符串。
那不同的是什么呢?不同的当然是varchar(n)类型的长度约束。你不能插入超过2000个字符到表t2中:

postgres=# insert into t2 select lpad('i',2000,'0'); 
INSERT 0 1
Time: 9.201 ms
postgres=# insert into t2 select lpad('i',2001,'0'); 
ERROR:  value too long for type character varying(2000)
Time: 3.445 ms

存储text字段的表t3就没有如此的限制:

postgres=# insert into t3 select lpad('i',2001,'0'); 
INSERT 0 1
Time: 9.818 ms

这也许似乎是varchar数据类型的一个优势,但你可以简单地通过添加一个约束为text使用相同行为:

postgres=# delete from t3 where a = lpad('i',2001,'0'); 
DELETE 1
Time: 200.586 ms
postgres=# alter table t3 add constraint c1 check ( length(a) < 2001 );
ALTER TABLE
Time: 289.121 ms
postgres=# insert into t3 select lpad('i',2001,'0');
ERROR:  new row for relation "t3" violates check constraint "c1"
DETAIL:  Failing row contains (0000000000000000000000000000000000000000000000000000000000000000...).
Time: 1.623 ms

当数据插入时,那有什么不同吗?

postgres=# truncate t2,t3;
TRUNCATE TABLE
Time: 58.063 ms
postgres=# insert into t2 select lpad(i::varchar,100,'0') from generate_series(1,1000000) i; 
INSERT 0 1000000
Time: 2281.157 ms (00:02.281)
postgres=# insert into t3 select lpad(i::text,100,'0') from generate_series(1,1000000) i; 
INSERT 0 1000000
Time: 2572.467 ms (00:02.572)

并不见得。那varchar(n)如此的原因是什么呢?服从SQL标准罢了。

原文标题:Do you really need varchar(n) in PostgreSQL?
原文作者:Daniel Westermann
原文链接:https://www.dbi-services.com/blog/do-you-really-need-varcharn-in-postgresql/
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论